k8s集成gitlab-runner、maven、docker、kubelet等全自动发布全流程实践



基于gitlab runner 的did(docker in docker ) ci/cd k8s方案 首先,jenkins很强大,尤其是各种插件的支持,但实际个人工作中,用到的并不多,早期大型项目布署负载各种脚本和 远程调用,目前所有项目和k8s深耦合,已经拆解为各种云服务,jenkins的大部分功能用不到 其次,这只是一种可行的方案,并不是最优的方案,不同阶段也都有再调整和优化的空间 最后,对个人的需求,jenkins过于复杂,gitlab-runner足可胜任,对研发人员更透明友好,学习应用成本更低。 CI/CD集成k8s服务,对各种文件抽像的只有三步(部分语言生态只需要一步) 1 编译环境加载依赖编译可执行文件 2 执行环境+可执行文件打包为docker image并上传至docker hub 3 执行k8s布署,启动服务 k8s布署k8s gitlab-runner 服务 apiVersion: apps/v1 kind: StatefulSet metadata: name: gitlab-runner-java namespace: common spec: replicas:1 revisionHistoryLimit:10 selector: matchLabels: app: gitlab-runner-java serviceName: gitlab-runner-java template: metadata: creationTimestamp:null labels: app: gitlab-runner-java name: gitlab-runner-java spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms:

  • matchExpressions:

  • key: kubernetes.io/hostname operator: In values:

  • server-svc-2 containers:

  • image: gitlab/gitlab-runner:v12.4.1 imagePullPolicy: IfNotPresent name: gitlab-runner-java resources: limits: cpu:“4” memory: 4Gi requests: cpu:“2” memory: 1Gi securityContext: runAsUser:0 volumeMounts:

  • mountPath:/var/run/docker.sock name: docker-sock readOnly:true

  • mountPath:/etc/gitlab-runner name: config

  • mountPath:/cache name: cache

  • mountPath:/usr/share/maven/ref/repository name: maven dnsPolicy: ClusterFirst restartPolicy: Always schedulerName:default-scheduler securityContext:{} terminationGracePeriodSeconds:30 volumes:

  • hostPath: path:/var/run/docker.sock type:"" name: docker-sock

  • hostPath: path:/data1/pv/gitlab-runner-config/gitlab-runner-java type:"" name: config

  • hostPath: path:/data1/pv/gitlab-runner-cache/gitlab-runner-java type:"" name: cache

  • hostPath: path:/data2/pv/gitlab-runner-java-maven type:"" name: maven 首先布署gitlab-runner节点,使用did方案,did要直接访问因此需要较高的权限 个人比较熟悉k8s,k8s权限提升使用 securityContext: runAsUser: 0 另外需挂载几项目录, /var/run/docker.sock:/var/run/docker.sock 自不必说,did 必备 :/etc/gitlab-runner 默认容器内配置文件路径为/etc/gitlab-runner/config.toml,挂载方便维护 /cache 自不必说 :/usr/share/maven/ref/repository maven的默认的本地缓存repository路径 需要补充的是,只有/var/run/docker.sock是did必须其他三项是为了可以从k8s访问本地挂载目录提供的,方便在没有宿 主机权限下,通过k8s pod访问目录 docker ps |grep java 759e94f0dafd db5c21d7a3e4 “/usr/bin/dumb-init …” 6 weeks ago Up 6 weeks k8s_gitlab- runner-java_gitlab-runner-java-0_common_9112c414-cef3-4801-a234-4c8edb965e82_0 6bda34787976 registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1 “/pause” 6 weeks ago Up 6 weeks k8s_POD_gitlab- runner-java-0_common_9112c414-cef3-4801-a234-4c8edb965e82_0 目前为不同语言构建了不同的pod,以java的为例(java的本身较为复杂,maven打包为jar,再将jar以java包装为镜像) gitlab-runner gitlab-runner-java-0 1/1 Running 0 47d gitlab-runner gitlab-runner-node-0 1/1 Running 0 47d

gitlab-runner gitlab-runner-python-0 1/1 Running 0 47d gitlab-runner gitlab-runner-golang-0 1/1 Running 0 47d 进入gitlab后台,获取项目的token

Screen Shot 2021-01-11 at 3.49.31 PM

进入容器以token 注册gitlab-runner

root@gitlab-runner-java-0:/# gitlab-runner register -n \

–url https://[abc.gitlab.com]
–registration-token [your_project_token]
–tag-list java,prod,preview,test
–executor docker
–description “java”
–cache-dir /data1/pv/gitlab-runner-cache/gitlab-runner-java/cache-dir
–docker-cache-dir /data1/pv/gitlab-runner-cache/gitlab-runner-java/docker-cache- dir
–docker-image “docker:18.06.3”
–docker-pull-policy “if-not-present”
–docker-volumes /var/run/docker.sock:/var/run/docker.sock
–docker-volumes /root/.docker/config.json:/root/.docker/config.json
–docker-volumes /data1/pv/gitlab-runner-cache/gitlab-runner-java:/cache
–docker-volumes /data2/pv/gitlab-runner-java-maven/repository:/root/.m2/repository Runtime platform arch=amd64 os=linux pid=94 revision=05161b14 version=12.4.1 Running in system-mode. Registering runner… succeeded runner=joTBdSTv 重点注意–docker-volumes的几项参数,因为是did的执行方式,外部目录尤其是(/root/.m2/repository)需要在maven容器 持久化,k8s yaml mount的路径只是查看需要,gitlab-runner注册时的docker-volumes才是真正在不同任务间共享数据 的必须 例如–docker-volumes /root/.docker/config.json:/root/.docker/config.json 宿主机docker login的授权信息,可以共享使用 ​ –docker-volumes /data2/pv/gitlab-runner-java-maven/repository:/root/.m2/repository 对maven容器,不同项目共同挂 载宿主机目录,减少jar包的重复下载 注册完后的内容如 root@gitlab-runner-java-0:/# cat /etc/gitlab-runner/config.toml concurrent = 8 check_interval = 0 [session_server]

session_timeout = 1800 [[runners]] name = “java” url = “https://[abc.123.com]” token = “[your token]” executor = “docker” cache_dir = “/data1/pv/gitlab-runner-cache/gitlab-runner-java/cache-dir” [runners.custom_build_dir] [runners.docker] tls_verify = false image = “docker:18.06.3” privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/var/run/docker.sock:/var/run/docker.sock", “/root/.docker/config.json:/root/.docker/config.json”, “/data1/pv/gitlab-runner- cache/gitlab-runner-java:/cache”, “/data2/pv/gitlab-runner-java- maven/repository:/root/.m2/repository”] cache_dir = “/data1/pv/gitlab-runner-cache/gitlab-runner-java/docker-cache-dir” pull_policy = “if-not-present” shm_size = 0 [runners.cache] [runners.cache.s3] [runners.cache.gcs] 重点的项目配置文件,具体项目结构见https://github.com/cclient/hanlp-remote-dict . ├── Dockerfile –打包docker镜像配置 ├── deploy –镜像打包成功,更新上线至k8s配置 │   ├── deployment_preview.yaml │   ├── deployment_prod.yaml │   └── deployment_test.yaml ├── .gitlab-ci.yml –gitlab-runner ci/cd配置 ├── pom.xml –java项目依赖配置

.gitlab-ci.yml variables: DOCKER_API_VERSION: ‘1.38’ DOCKER_HUB: “[docker hub/registry]” DOCKER_USERNAME: “[your docker user]” DOCKER_PASSWORD: “[your docker passwd]” PROJECT_NAME: $CI_PROJECT_NAME PROJECT_VERSION: $CI_COMMIT_TAG K8S_API_URL: “https://[k8s api host]:6443” K8S_ACCESS_TOKEN: “[your k8s token]” K8S_DEPLOY_BASE_NAME: “hanlp-remote-dict” CONTAINER_IMAGE: $DOCKER_HUB/$CI_PROJECT_NAME image: docker:18.06.3 stages:

  • prepare-env
  • maven-package
  • docker-build
  • k8s-deploy

根据git branch/tag匹配 cicd上线环境,添加相关环境变量

prepare-env-develop: stage: prepare-env tags:

  • java only:
  • develop before_script:
  • mkdir -p ./sartifacts/ script:
  • echo BUILD_ENV=“test” » ./sartifacts/version
  • echo BUILD_IMAGE_VERSION=“test” » ./sartifacts/version artifacts: name: “${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}” paths:
  • ./sartifacts/* expire_in: 2 day prepare-env-preview:

stage: prepare-env tags:

  • java only:
  • /^release/.*$/
  • master before_script:
  • echo docker login hub.intra.github.com -u “’$DOCKER_USERNAME’” -p $DOCKER_PASSWORD
  • mkdir -p ./sartifacts/ script:
  • echo BUILD_ENV=“preview” » ./sartifacts/version
  • echo BUILD_IMAGE_VERSION=“preview” » ./sartifacts/version artifacts: name: “${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}” paths:
  • ./sartifacts/* expire_in: 2 day prepare-env-production: stage: prepare-env tags:
  • java only:
  • tags before_script:
  • mkdir -p ./sartifacts/ script:
  • echo BUILD_ENV=“prod” » ./sartifacts/version
  • echo BUILD_IMAGE_VERSION=$CI_COMMIT_TAG » ./sartifacts/version artifacts: name: “${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}” paths:
  • ./sartifacts/* expire_in: 2 day

以下为通用部分

mvn 打包java项目,生成jar包,并将jar包添加至./sartifacts/ 传至下一级

maven-package: image: maven:3.6.3-adoptopenjdk-8 stage: maven-package tags:

  • java only:
  • tags
  • /^release/.*$/
  • master
  • develop before_script:

source加载./sartifacts/version的环境变量

  • source ./sartifacts/version

echo只是调试用,打印gitlab-runner结合git的变量

  • echo ‘CI_CONFIG_PATH’ $CI_CONFIG_PATH
  • echo ‘CI_COMMIT_REF_NAME’ $CI_COMMIT_REF_NAME
  • echo ‘CI_COMMIT_BRANCH’ $CI_COMMIT_BRANCH
  • echo ‘CI_COMMIT_TAG’ $CI_COMMIT_TAG
  • echo ‘CI_COMMIT_TITLE’ $CI_COMMIT_TITLE
  • echo ‘CI_ENVIRONMENT_NAME’ $CI_ENVIRONMENT_NAME
  • echo ‘CONTAINER_IMAGE’ $CONTAINER_IMAGE
  • echo ‘ENV’ $ENV
  • echo ‘MAVEN_OPTS’ “$MAVEN_OPTS”
  • echo ‘BUILD_ENV’ “$BUILD_ENV”
  • echo ‘BUILD_IMAGE_VERSION’ “$BUILD_IMAGE_VERSION” script:
  • mvn clean package -Dmaven.test.skip=true -Dmaven.repo.local=/root/.m2/repository
  • mv target ./sartifacts/ artifacts: name: “${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}” paths:
  • ./sartifacts/* expire_in: 2 day

获取上一级的sartifacts,提取target目录,打包image并上传

docker-build: image: docker:18.06.3 stage: docker-build tags:

  • java only:

  • tags

  • /^release/.*$/

  • master

  • develop before_script:

  • pwd

  • ls -alh

  • source ./sartifacts/version

  • mv ./sartifacts/target ./target script:

  • docker build -t $CONTAINER_IMAGE:$BUILD_IMAGE_VERSION ./

- docker login hub.intra.github.com -u $DOCKER_USERNAME -p $DOCKER_PASSWORD

  • docker push $CONTAINER_IMAGE:$BUILD_IMAGE_VERSION

执行k8s yaml 上线pod/deploy/sts等服务

k8s-deploy: image: telefonica/kubectl:1.15.10 stage: k8s-deploy tags:

  • java only:
  • tags
  • /^release/.*$/
  • master
  • develop before_script:
  • source ./sartifacts/version
  • echo K8S_DEPLOY_BASE_NAME-BUILD_ENV “$K8S_DEPLOY_BASE_NAME-$BUILD_ENV”
  • echo CONTAINER_IMAGE “$CONTAINER_IMAGE” script:
  • kubectl –server="${K8S_API_URL}" –token="$K8S_ACCESS_TOKEN" –insecure-skip-tls- verify apply -f ./deploy/deployment_$BUILD_ENV.yaml -n default
  • kubectl –server="${K8S_API_URL}" –token="$K8S_ACCESS_TOKEN" –insecure-skip-tls- verify set image deployment/$K8S_DEPLOY_BASE_NAME-$BUILD_ENV $K8S_DEPLOY_BASE_NAME-$BUILD_ENV=$CONTAINER_IMAGE:$BUILD_IMAGE_VERSION -n default – record 基本流程为 1 第一阶段 prepare-env 根据gitlab 提交和分枝信息,构造version文件(保存env),供后续阶段使用,目前实现了三项 prepare-env-develop,prepare-env-preview,prepare-env-production,内容一样是写入version,为了保证这里不够 精简,看了下gitlab-runner的rule,应该可以有更优雅的方案,但个人没有精力深入,有知道更好办法的,烦请告 知。 2 第二阶段 maven-package did以maven:3.6.3-adoptopenjdk-8为底包打包项目,生成target目录(下有jar包),并将 targer整个打包为sartifacts传给下一阶段,大包的话,只用jar即可 3 第三阶段 docker-build dit以docker:18.06.3为底包,自动下载解压sartifacts,拷备出target至项目目标地址,根据 Dockerfile(对该示例而言底包为openjdk:8u162-jre-slim-stretch) 生成image 并上传,

docker login hub.intra.github.com -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 这一步如果挂载了宿主机的docker 授权则不必要 4 第四阶段 k8s-deploy did 以telefonica/kubectl:1.15.10为底包,因需选择版本,个人用该镜像从k8s v1.15.0用到 v1.18.5,目前使用正常,通过api 提交相应deploy.yaml至k8s api,这一阶段执行两部,kubectl apply -f deploy.yaml 提交上线,再kubectl set image 更新deploy内服务image的真实版本 docker file FROM openjdk:8u162-jre-slim-stretch WORKDIR /home/spring ADD target/*.jar /home/spring/app.jar deploy.yaml 以测试deploy为例 apiVersion: apps/v1 kind: Deployment metadata: labels: app: hanlp-remote-dict-test name: hanlp-remote-dict-test namespace: default spec: replicas: 1 selector: matchLabels: app: hanlp-remote-dict-test strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: hanlp-remote-dict-test spec: containers:

  • image: cclient/arm64v8-kafka:test command:
  • java args:
  • -Xms1g
  • -Xmx2g
  • -XX:+UseG1GC
  • -XX:G1ReservePercent=10
  • -XX:+UseStringDeduplication
  • -jar
  • app.jar imagePullPolicy: Always name: hanlp-remote-dict-test resources: limits: cpu: 1 memory: 2Gi requests: cpu: 1 memory: 1Gi restartPolicy: Always

apiVersion: v1 kind: Service metadata: name: hanlp-remote-dict-test namespace: default spec: externalTrafficPolicy: Local ports:

  • port: 8080 protocol: TCP targetPort: 8080 selector: app: hanlp-remote-dict-test sessionAffinity: None type: NodePort

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: hanlp-remote-dict-test

namespace: default spec: rules:

  • host: hanlp-remote-dict-test.github.com http: paths:
  • path: / backend: serviceName: hanlp-remote-dict-test servicePort: 8080 执行成功样例

Screen Shot 2021-01-11 at 5.58.36 PM

[session_server].listen_address not defined, session endpoints disabled builds=0 Checking for jobs… received job=196962 repo_url=https://github.com/cclient/hanlp-remote-dict.git runner=sdWbANrN Job succeeded duration=21.427262629s job=196962 project=11190 runner=sdWbANrN Checking for jobs… received job=196963 repo_url=https://github.com/cclient/hanlp-remote-dict.git runner=sdWbANrN Job succeeded duration=2m3.362519176s job=196963 project=11190 runner=sdWbANrN Checking for jobs… received job=196964 repo_url=https://github.com/cclient/hanlp-remote-dict.git runner=sdWbANrN Job succeeded duration=4m8.502822318s job=196964 project=11190 runner=sdWbANrN Checking for jobs… received job=196965 repo_url=https://github.com/cclient/hanlp-remote-dict.git runner=sdWbANrN 其他 1 目前spring boot 构造为一个大包,docker image上传下载io开销较大,可调整为先下载准备了依赖,再打小包 2 个人没有使用https://github.com/fabric8io/docker-maven-plugin等mvn 组件,主要原因是要支持多语言,方案不同, 但共用通用的ci/cd抽像,对其他语言而言,可能不存在相同定位的包管理插件,docker cmd 更通用,对环境的依赖也 小 3 test,preview,prod 不同上线分枝可以提交不同的配置 限于篇幅太长,最后test,preview,prod对应git 分枝管理,以后再弹独写一篇