背景
组件:Kubernetes,Gitlab,Jenkins
最近基于Flask开发了小型web后端。通过CI/CD进行发布部署,记录下整个CI/CD流程。
流程
Gitlab和Jenkins项目关联
设置项目
构建Dockerfile
提前构建好Docker镜像, 包含环境需要的依赖。
FROM 172.30.140.21/xxx/security:1.0 #从本地Harbor仓库获取自己构建的基础镜像
WORKDIR /opt/flask-security
COPY . .
CMD [ "sh", "-c", "uwsgi --ini uwsgi/uwsgi.ini && tail -f uwsgi/uwsgi.log" ]
Jenkinsfile
# 此Jenkinsfile文件在被Jenkins拉取项目源码后执行
def label = "slave-${UUID.randomUUID().toString()}"
// 调用的基础镜像
podTemplate(label: label, containers: [
containerTemplate(name: ‘kubectl‘, image: ‘172.30.140.21/xxx/kubectl:v1.15.3‘, command: ‘cat‘, ttyEnabled: true),
containerTemplate(name: ‘docker‘, image: ‘172.30.140.21/xxx/docker-cli:19.03.8‘, command: ‘cat‘, ttyEnabled: true),
containerTemplate(name: ‘python-env‘, image: ‘172.30.140.21//security:1.0‘, command: ‘cat‘, ttyEnabled: true),
containerTemplate(name: ‘jnlp‘, image: ‘172.30.140.21/xxx/jnlp-slave:4.0.1-1‘, alwaysPullImage: false, privileged: true, args: ‘${computer.jnlpmac} ${computer.name}‘)
], serviceAccount: ‘jenkins‘, volumes: [
hostPathVolume(mountPath: ‘/home/jenkins/.kube‘, hostPath: ‘/root/.kube‘),
hostPathVolume(mountPath: ‘/var/run/docker.sock‘, hostPath: ‘/var/run/docker.sock‘)
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
def imageUri = "172.30.140.21"
def imageHub = "xxx"
def imageApp = "flask-security"
def image = "${imageUri}/${imageHub}/${imageApp}"
stage(‘单元测试‘) {
echo "Part1.单元测试-test"
}
stage(‘代码编译打包‘) {
try {
container(‘python-env‘) {
echo "Part2.代码编译打包"
}
} catch (exc) {
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
stage(‘构建Docker镜像‘) {
withCredentials([usernamePassword(credentialsId: ‘xxx-habor‘, passwordVariable: ‘DOCKER_HUB_PASSWORD‘, usernameVariable: ‘DOCKER_HUB_USER‘)]) {
container(‘docker‘) {
echo "Part3.构建Docker镜像"
sh """
docker login ${imageUri} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
}
}
}
stage(‘修改部署文件‘) {
echo "Part4.修改YAML文件参数"
def ciEnv = "dev"
if (gitBranch == "origin/master") {
ciEnv = "prod"
}
sh "sed -i ‘s/<IMAGE_URI>/${imageUri}/g‘ manifests/flask-security.yaml"
sh "sed -i ‘s/<IMAGE_HUB>/${imageHub}/g‘ manifests/flask-security.yaml"
sh "sed -i ‘s/<IMAGE_APP>/${imageApp}/g‘ manifests/flask-security.yaml"
sh "sed -i ‘s/<BUILD_TAG>/${imageTag}/g‘ manifests/flask-security.yaml"
sh "sed -i ‘s/<BRANCH_NAME>/${ciEnv}/g‘ manifests/flask-security.yaml"
}
stage(‘推送Kubernetes‘) {
container(‘kubectl‘) {
echo "Part5.部署应用到 K8S"
sh "kubectl apply -f manifests/flask-security.yaml"
sh "kubectl apply -f manifests/flask-security-service.yaml"
sh "kubectl apply -f manifests/flask-security-ingress.yaml"
sh "kubectl rollout status -f manifests/flask-security.yaml"
echo "6.部署成功"
}
}
}
}
配置Kubernetes运行的manifests文件
- flask-security.yml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-security-<BRANCH_NAME>
namespace: elk-portal
labels:
app: flask-security
spec:
replicas: 1
selector:
matchLabels:
app: flask-security
template:
metadata:
labels:
app: flask-security
spec:
containers:
- name: flask-security
image: <IMAGE_URI>/<IMAGE_HUB>/<IMAGE_APP>:<BUILD_TAG>
imagePullPolicy: Always
ports:
- containerPort: 80
- flask-security-service.yml
kind: Service
apiVersion: v1
metadata:
namespace: elk-portal
name: flask-security-service
spec:
selector:
app: flask-security
ports:
- protocol: TCP
port: 80
targetPort: 80