Commit 7d8912e2 authored by 智勇's avatar 智勇

Merge branch 'stage2' into 'master'

Stage2



See merge request !20
parents b985d1ec dd0a6c66
const Router = require('koa-router')
const yaml = require('js-yaml')
const _ = require('lodash')
const logger = require('koa-log4').getLogger()
const templates = require('../serviceTemplate')
const lruCache = require('../services/lruCache.service')
const { ingressCreate, ingressDelete } = require('../kubeService/ingress')
const { projectConfig, defaultConfig } = require('../serviceTemplate/resourceLimit')
const { podGet, serviceCreate } = require('../kubeService/service')
const {
getPods, serviceCreate, getServices, getServiceDetail, serviceUpdate, serviceRestart, serviceDelete,
} = require('../kubeService/service')
const router = new Router()
module.exports = router
router.get('/', async (ctx) => {
// 取节点列表的第一个作为服务的访问ip
const cacheKey = 'k8s.nodes.first'
const c = lruCache.get(cacheKey)
let lanIp = lruCache.get(cacheKey)
if (!c) {
const res = await ctx.cluster.node_list()
lanIp = _.get(res, 'nodes[0].lanIp', '')
lruCache.set(cacheKey, lanIp)
}
const data = await ctx.cluster.service_list(ctx.query.namespace)
const podData = await podGet(ctx.query.namespace)
const getDetail = async (item) => {
const resData = await ctx.cluster.service_get(item.serviceName, ctx.query.namespace)
item.image = resData.service.containers[0].image
if (item.userLabels.type === 'base') {
item.portMappings = resData.service.portMappings
const pod = podData.body.items.filter(i => i.metadata.name.indexOf(item.serviceName) !== -1)
lanIp = pod[0].status.hostIP
item.lanIp = lanIp
}
}
const task = []
for (let i = 0; i < data.services.length; i += 1) {
task.push(getDetail(data.services[i]))
}
await Promise.all(task)
ctx.body = ctx.ok(data)
const data = await getServices(ctx.query.namespace)
ctx.body = ctx.ok({ services: data })
})
const createService = async (ctx) => {
const {
type, serviceName, namespace, domain, label,
} = ctx.request.body
logger.info('创建服务', ctx.request.body)
const makeResouce = (serviceName, type) => {
// 资源限制
const resources = projectConfig[serviceName] || defaultConfig[type]
logger.info('资源限制', JSON.stringify(resources))
const data = ctx.request.body
data.resources = resources
await serviceCreate(data)
if (label !== 'base') {
if (serviceName === 'xyqb-user2') {
await ingressCreate(namespace, 'xyqb-user2-2', 'passportapi2')
}
await ingressCreate(namespace, serviceName, domain)
}
ctx.body = ctx.ok('创建成功')
return resources
}
const createServiceTencent = async (ctx) => {
const createService = async (ctx) => {
const {
type, serviceName, namespace, system_name, domain, label,
type, serviceName, namespace, domain, label,
} = ctx.request.body
logger.info('创建服务', ctx.request.body)
// 资源限制
const resources = projectConfig[serviceName] || defaultConfig[type]
logger.info('资源限制', JSON.stringify(resources))
const data = ctx.request.body
const resources = makeResouce(serviceName, type)
data.resources = resources
if (!system_name) {
// ui abTest的时候不一样
data.system_name = serviceName
}
const template = templates[type].replace(/{{([A-Za-z0-9_\.]+)}}/g, function () {
if (_.get(data, arguments[1], null) === null) {
throw new Error(`缺少模板所需变量: ${arguments[1]}`)
}
return _.get(data, arguments[1])
})
let params = yaml.load(template)
// todo: 放开限制,暂时只更新了clotho的镜像
// java项目的就绪检查需要定制
if (type === 'java' && data.system_name !== 'clotho') {
params = _.omitBy(params, (value, key) => key.indexOf('healthCheck') !== -1)
}
logger.info(params)
await ctx.cluster.service_create(params, label)
logger.info('创建服务', data)
await serviceCreate(data)
if (label !== 'base') {
if (serviceName === 'xyqb-user2') {
await ingressCreate(namespace, 'xyqb-user2-2', 'passportapi2')
......@@ -121,17 +52,13 @@ router.post('/details', async (ctx) => {
namespace: ctx.Joi.string().required(),
}))
const podData = await podGet(ctx.request.body.namespace)
// const lanIp = podData.body.items.filter(i => i.metadata.name.indexOf(ctx.request.body.serviceName) !== -1)[0].status.hostIP
const serviceItems = podData.body.items.filter(i => i.metadata.name.indexOf(ctx.request.body.serviceName) !== -1)
const lanIp = _.get(serviceItems, '[0].status.hostIP', '')
const data = await ctx.cluster.service_get(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok(Object.assign({}, data, { lanIp }))
const data = await getServiceDetail(ctx.request.body.namespace, ctx.request.body.serviceName)
ctx.body = ctx.ok(data)
})
router.post('/delete', async (ctx) => {
await ctx.cluster.service_delete(ctx.request.body.serviceName, ctx.request.body.namespace)
const rsName = ctx.request.body.podName.slice(0, -6)
await serviceDelete(ctx.request.body.namespace, ctx.request.body.serviceName, rsName)
if (ctx.request.body.serviceName === 'xyqb-user2') {
await ingressDelete(ctx.request.body.namespace, 'xyqb-user2-2')
}
......@@ -140,22 +67,26 @@ router.post('/delete', async (ctx) => {
})
router.post('/modifyImage', async (ctx) => {
let list = await ctx.cluster.service_list(ctx.request.body.namespace)
list = list.services.map(item => item.serviceName)
let list = await getPods(ctx.request.body.namespace)
list = list.map(item => (item.metadata.labels && item.metadata.labels['qcloud-app']) || item.metadata.name)
if (list.includes(ctx.request.body.serviceName)) {
await ctx.cluster.service_modifyImage(ctx.request.body.serviceName, ctx.request.body.image, ctx.request.body.namespace)
const data = ctx.request.body
if (!data.label) {
data.label = data.type
}
const resources = makeResouce(data.serviceName, data.label)
data.resources = resources
logger.info('更新服务', data)
await serviceUpdate(data)
} else {
await createService(ctx)
}
ctx.body = ctx.ok('更新成功')
})
router.post('/instance', async (ctx) => {
const data = await ctx.cluster.instance_get(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok(data)
})
router.post('/redeploy', async (ctx) => {
await ctx.cluster.service_redeployment(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok('服务重部署成功')
await serviceRestart(ctx.request.body.namespace, ctx.request.body.podName)
ctx.body = ctx.ok('重置服务成功')
})
const Router = require('koa-router')
const yaml = require('js-yaml')
const _ = require('lodash')
const logger = require('koa-log4').getLogger()
const templates = require('../serviceTemplate')
const lruCache = require('../services/lruCache.service')
const { ingressCreate, ingressDelete } = require('../kubeService/ingress')
const { projectConfig, defaultConfig } = require('../serviceTemplate/resourceLimit')
const { podGet, serviceCreate } = require('../kubeService/service')
const router = new Router()
module.exports = router
router.get('/', async (ctx) => {
// 取节点列表的第一个作为服务的访问ip
const cacheKey = 'k8s.nodes.first'
const c = lruCache.get(cacheKey)
let lanIp = lruCache.get(cacheKey)
if (!c) {
const res = await ctx.cluster.node_list()
lanIp = _.get(res, 'nodes[0].lanIp', '')
lruCache.set(cacheKey, lanIp)
}
const data = await ctx.cluster.service_list(ctx.query.namespace)
const podData = await podGet(ctx.query.namespace)
const getDetail = async (item) => {
const resData = await ctx.cluster.service_get(item.serviceName, ctx.query.namespace)
item.image = resData.service.containers[0].image
if (item.userLabels.type === 'base') {
item.portMappings = resData.service.portMappings
const pod = podData.body.items.filter(i => i.metadata.name.indexOf(item.serviceName) !== -1)
lanIp = pod[0].status.hostIP
item.lanIp = lanIp
}
}
const task = []
for (let i = 0; i < data.services.length; i += 1) {
task.push(getDetail(data.services[i]))
}
await Promise.all(task)
ctx.body = ctx.ok(data)
})
const createService = async (ctx) => {
const {
type, serviceName, namespace, image, system_name, domain, label, debug,
} = ctx.request.body
logger.info('创建服务', ctx.request.body)
if (label === 'base') {
await serviceCreate(namespace, serviceName, image, label)
ctx.body = ctx.ok('创建成功')
return
}
const data = {
serviceName,
namespace,
image,
system_name,
debug,
}
if (!system_name) {
// ui abTest的时候不一样
data.system_name = serviceName
}
// 资源限制
const resources = projectConfig[data.system_name] || defaultConfig[type]
logger.info('资源限制', JSON.stringify(resources))
data.resources = resources
const template = templates[type].replace(/{{([A-Za-z0-9_\.]+)}}/g, function () {
if (_.get(data, arguments[1], null) === null) {
throw new Error(`缺少模板所需变量: ${arguments[1]}`)
}
return _.get(data, arguments[1])
})
let params = yaml.load(template)
// todo: 放开限制,暂时只更新了clotho的镜像
// java项目的就绪检查需要定制
if (type === 'java' && data.system_name !== 'clotho') {
params = _.omitBy(params, (value, key) => key.indexOf('healthCheck') !== -1)
}
logger.info(params)
await ctx.cluster.service_create(params, label)
if (label !== 'base') {
if (serviceName === 'xyqb-user2') {
await ingressCreate(namespace, 'xyqb-user2-2', 'passportapi2')
}
await ingressCreate(namespace, serviceName, domain)
}
ctx.body = ctx.ok('创建成功')
}
router.post('/create', async (ctx) => {
await createService(ctx)
})
router.post('/details', async (ctx) => {
ctx.validate(ctx.Joi.object().keys({
serviceName: ctx.Joi.string().required(),
namespace: ctx.Joi.string().required(),
}))
const podData = await podGet(ctx.request.body.namespace)
// const lanIp = podData.body.items.filter(i => i.metadata.name.indexOf(ctx.request.body.serviceName) !== -1)[0].status.hostIP
const serviceItems = podData.body.items.filter(i => i.metadata.name.indexOf(ctx.request.body.serviceName) !== -1)
const lanIp = _.get(serviceItems, '[0].status.hostIP', '')
const data = await ctx.cluster.service_get(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok(Object.assign({}, data, { lanIp }))
})
router.post('/delete', async (ctx) => {
await ctx.cluster.service_delete(ctx.request.body.serviceName, ctx.request.body.namespace)
if (ctx.request.body.serviceName === 'xyqb-user2') {
await ingressDelete(ctx.request.body.namespace, 'xyqb-user2-2')
}
await ingressDelete(ctx.request.body.namespace, ctx.request.body.serviceName)
ctx.body = ctx.ok('删除成功')
})
router.post('/modifyImage', async (ctx) => {
let list = await ctx.cluster.service_list(ctx.request.body.namespace)
list = list.services.map(item => item.serviceName)
if (list.includes(ctx.request.body.serviceName)) {
await ctx.cluster.service_modifyImage(ctx.request.body.serviceName, ctx.request.body.image, ctx.request.body.namespace)
} else {
await createService(ctx)
}
ctx.body = ctx.ok('更新成功')
})
router.post('/instance', async (ctx) => {
const data = await ctx.cluster.instance_get(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok(data)
})
router.post('/redeploy', async (ctx) => {
await ctx.cluster.service_redeployment(ctx.request.body.serviceName, ctx.request.body.namespace)
ctx.body = ctx.ok('服务重部署成功')
})
const commonService = ['db', 'redis', 'rabbitmq', 'zookeeper', 'kong', 'postgres', 'mongodb']
module.exports = {
commonService,
}
......@@ -2,9 +2,11 @@ const path = require('path')
const Client = require('kubernetes-client').Client
const config = require('kubernetes-client').config
const _ = require('lodash')
const moment = require('moment')
const yaml = require('js-yaml')
const logger = require('koa-log4').getLogger('kubeService')
const yamls = require('../yamls')
const dict = require('./dictService')
const client = new Client({
config: config.fromKubeconfig(
......@@ -13,12 +15,11 @@ const client = new Client({
version: '1.10',
})
const serviceGet = async (namespace, serviceName) => client.api.v1.namespaces(namespace).services(serviceName).get()
const podGet = async namespace => client.api.v1.namespaces(namespace).pods.get()
const serviceCreate = async (data) => {
const { type, namespace, serviceName } = data
const yamlManifest = yamls[type].replace(/{{([A-Za-z0-9_\.]+)}}/g, function () {
const makeManifest = (data) => {
if (!data.debug) {
data.debug = '"0"'
}
const yamlManifest = yamls[data.type].replace(/{{([A-Za-z0-9_\.]+)}}/g, function () {
if (_.get(data, arguments[1], null) === null) {
throw new Error(`缺少模板所需变量: ${arguments[1]}`)
}
......@@ -26,6 +27,12 @@ const serviceCreate = async (data) => {
})
const yamlArray = yamlManifest.split('---')
return yamlArray
}
const serviceCreate = async (data) => {
const { namespace, serviceName } = data
const yamlArray = makeManifest(data)
for (const item of yamlArray) {
const jsonObj = yaml.load(item);
......@@ -58,8 +65,158 @@ const serviceCreate = async (data) => {
}
}
const serviceUpdate = async (data) => {
const { namespace, serviceName } = data
const yamlArray = makeManifest(data)
for (const item of yamlArray) {
const jsonObj = yaml.load(item);
if (jsonObj.kind === 'Deployment') {
logger.info('Deployment:', JSON.stringify(jsonObj))
await client.apis.apps.v1beta1.namespaces(namespace).deployments(serviceName).put({ body: jsonObj })
}
}
}
const formatServiceInfo = (obj) => {
const portObj = {}
obj.spec.ports.forEach((i) => {
portObj[`${i.name}_port`] = i.nodePort
})
return _.assign(portObj, {
clusterIp: obj.spec.clusterIP,
portMappings: obj.spec.ports,
// labels: obj.metadata.labels,
})
}
const getPodStatus = (podInfo) => {
if (podInfo.status.phase === 'Pending') {
return 'Pending'
}
if (podInfo.status.conditions[2].status === 'False') {
return 'PodScheduling'
}
if (podInfo.status.conditions[0].status === 'False') {
return 'Initializing'
}
if (podInfo.status.conditions[1].status === 'False') {
return 'Waiting'
}
if (podInfo.metadata.deletionTimestamp) {
return 'Terminating'
}
return 'Normal'
}
const formatPodInfo = (podInfo) => {
// if (podInfo.metadata.name.indexOf('xyqb') !== -1) {
// console.log(1, JSON.stringify(podInfo), podInfo.metadata.labels['qcloud-app'])
// }
const podStatus = getPodStatus(podInfo)
// const containerImage = _.get(podInfo.status.containerStatuses, '[0].image', '')
const containerImage = _.get(podInfo.spec.containers, '[0].image', '')
const ret = {
serviceName: (podInfo.metadata.labels && podInfo.metadata.labels['qcloud-app']) || podInfo.metadata.name,
podName: podInfo.metadata.name,
status: podStatus,
podIp: podInfo.status.podIP,
lanIp: podInfo.status.hostIP,
startTime: podInfo.status.startTime,
createdAt: moment(new Date(podInfo.status.startTime)).startOf('minute').fromNow(),
image: containerImage,
labels: podInfo.metadata.labels,
}
if (containerImage !== '') {
ret.branch = containerImage.split(':')[1]
ret.branch_no_time = containerImage.split(':')[1].split('-')[0]
ret.branch_time = containerImage.split(':')[1].split('-')[1]
}
return ret
}
const formatIngressInfo = obj => ({ host: _.get(obj.spec, 'rules[0].host', '') })
const getPods = async (namespace) => {
const podData = await client.api.v1.namespaces(namespace).pods.get()
return podData.body.items
}
const getServices = async (namespace) => {
const ret = []
const service = {}
const res = await Promise.all([
client.api.v1.namespaces(namespace).pods.get(),
client.api.v1.namespaces(namespace).services.get(),
client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get(),
])
res[0].body.items.forEach(async (item) => {
const serviceName = (item.metadata.labels && item.metadata.labels['qcloud-app']) || item.metadata.name
service[serviceName] = formatPodInfo(item)
})
res[1].body.items.forEach(async (item) => {
if (service[item.metadata.name]) {
service[item.metadata.name] = _.assign(service[item.metadata.name], formatServiceInfo(item))
}
})
res[2].body.items.forEach(async (item) => {
if (service[item.metadata.name]) {
service[item.metadata.name] = _.assign(service[item.metadata.name], formatIngressInfo(item))
}
})
for (const index in service) {
if (Object.prototype.hasOwnProperty.call(service, index)) {
ret.push(service[index])
}
}
return ret
}
const getServiceDetail = async (namespace, name) => {
const res = await Promise.all([
client.api.v1.namespaces(namespace).pods.get({ qs: { labelSelector: `qcloud-app=${name},tier!=job` } }),
client.api.v1.namespaces(namespace).services(name).get(),
])
res[0] = formatPodInfo(res[0].body.items[0])
res[1] = formatServiceInfo(res[1].body)
if (!dict.commonService.includes(name)) {
res[3] = await client.apis.extensions.v1beta1.namespaces(namespace).ingresses(name).get()
res[3] = formatIngressInfo(res[3].body)
}
return _.assign({}, res[0], res[3], res[1])
}
const serviceRestart = async (namespace, name) => {
await client.api.v1.namespaces(namespace).pods(name).delete()
}
const serviceDelete = async (namespace, name, rsName) => {
await client.apis.apps.v1beta1.namespaces(namespace).deployments(name).delete()
await client.apis.apps.v1.namespaces(namespace).replicasets(rsName).delete()
await client.api.v1.namespaces(namespace).services(name).delete()
}
module.exports = {
serviceGet,
podGet,
getServiceDetail,
getServices,
getPods,
serviceCreate,
serviceUpdate,
serviceRestart,
serviceDelete,
}
......@@ -3092,6 +3092,11 @@
}
}
},
"moment": {
"version": "2.24.0",
"resolved": "http://npmprivate.quantgroups.com/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"ms": {
"version": "2.0.0",
"resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz",
......
......@@ -13,6 +13,7 @@
"kubernetes-client": "^6.3.2",
"lodash": "^4.17.11",
"lru-cache": "^5.1.1",
"moment": "^2.24.0",
"request": "^2.88.0"
},
"devDependencies": {
......
......@@ -16,9 +16,11 @@ spec:
template:
metadata:
labels:
type: {{label}}
qcloud-app: {{serviceName}}
type: {{label}}
spec:
terminationGracePeriodSeconds: 0
revisionHistoryLimit: 1
containers:
- name: {{serviceName}}
......
......@@ -33,6 +33,7 @@ spec:
metadata:
labels:
qcloud-app: {{serviceName}}
type: base
spec:
containers:
- image: ccr.ccs.tencentyun.com/{{image}}
......
......@@ -33,6 +33,7 @@ spec:
metadata:
labels:
qcloud-app: {{serviceName}}
type: base
spec:
# hostname固定,容器重置后数据持久化才能正常
hostname: {{serviceName}}-{{namespace}}
......
......@@ -32,6 +32,7 @@ spec:
template:
metadata:
labels:
type: base
qcloud-app: {{serviceName}}
spec:
containers:
......
......@@ -19,6 +19,7 @@ spec:
qcloud-app: {{serviceName}}
type: {{label}}
spec:
terminationGracePeriodSeconds: 0
revisionHistoryLimit: 1
containers:
- name: {{serviceName}}
......
......@@ -33,6 +33,7 @@ spec:
metadata:
labels:
qcloud-app: {{serviceName}}
type: base
spec:
containers:
- image: ccr.ccs.tencentyun.com/{{image}}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment