在devops开发流程中,ci/cd是很非常重要的步骤,它赋予了我们快速迭代的可能:
- ci(持续构建) 代码提交后触发自动化的单元测试,代码预编译,构建镜像,上传镜像等.
- cd(持续发布) 持续发布则指将构建好的程序发布到各种环境,如预发布环境,正式环境.
GitLab CI/CD 通过在项目内 .gitlab-ci.yaml 配置文件读取 CI 任务并进行相应处理;GitLab CI 通过其称为 GitLab Runner 的 Agent 端进行 build 操作,结构如下:
GitLab Runner
gitlab 的CI/CD任务需要GitLab Runner运维,Runner可以是虚拟机、VPS、裸机、docker容器、甚至一堆容器。GitLab和Runners通过API通信,所以唯一的要求就是运行Runners的机器可以联网,,参考官方文档进行安装
#机器为 CentOS 7.4
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
yum install gitlab-runner
gitlab-runner status
Runner 通过注册的方式添加需要服务的Project、Group或者整个gitlab,在Project/Group->CI/CD->Runners settings查找需要的registration token 执行官方文档的设置步骤
$ gitlab-runner register
Running in system-mode.
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
# 输入 URL http://gitlab.domain.com/
Please enter the gitlab-ci token for this runner:
#输入 registration token
Please enter the gitlab-ci description for this runner:
#输入描述
Please enter the gitlab-ci tags for this runner (comma separated):
#输入tags
Registering runner... succeeded runner=9jHBE4P6
Please enter the executor: ssh, virtualbox, docker+machine, docker-ssh+machine, docker, docker-ssh, parallels, shell, kubernetes:
#输入执行类型,我输入的是 docker
Please enter the default Docker image (e.g. ruby:2.1):
#默认运行的镜像
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
#查看注册
$ gitlab-runner list
在 Project/Group->CI/CD->Runners settings 查看是否可以使用
.gitlab-ci.yml
.gitlab-ci.yml是用来配置CI在我们的项目中做些什么工作,它位于项目的根目录。
语法参考官方文档的翻译
我司项目目前有 java,react,android,ios 类型项目,该篇介绍 java
java build
项目使用maven构建,发布需要打包成docker images,采用dockerfile-maven-plugin方式做 docker images bulid and push
image: maven:3.5.4-jdk-8
# 注意使用 docker:dind 需要设置 /etc/gitlab-runner/config.toml 的 privileged = true,
services:
- docker:dind
variables:
# This will supress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
#用于支持 docker bulid
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
stages:
- build
- deploy
# Validate JDK8
validate:jdk8:
script:
- 'mvn $MAVEN_CLI_OPTS test-compile'
only:
- dev-4.0.1
stage: build
# 目前只做到打包
deploy:jdk8:
# Use stage test here, so the pages job may later pickup the created site.
stage: deploy
script:
- 'mvn $MAVEN_CLI_OPTS deploy'
only:
- dev-4.0.1
注意使用docker:dind 需要在 docker run 加入 –privileged,Runner 设置方式为修改 /etc/gitlab-runner/config.toml 的 privileged = true
concurrent = 1
check_interval = 0
[[runners]]
name = "kms"
url = "http://example.org/ci"
token = "3234234234234"
executor = "docker"
[runners.docker]
tls_verify = false
image = "alpine:3.4"
privileged = true
disable_cache = false
volumes = ["/cache"]
[runners.cache]
Insecure = false
dockerfile-maven-plugin push 支持
dockerfile-maven-plugin 的 push 需要在 maven settings.xml 文件中设置 docker 的用户名和密码,这里需要重做 maven images ,为了安全还需要对 settings.xml 中的密码进行加密,并且 dockerfile-maven-plugin 高于 1.4.3 才支持这个设置
进行 settings-security.xml settings.xml 文件准备
#生成 master 密码
$ mvn --encrypt-master-password 123456
{40hwd7X9T1wHfRTKWlUIbHyjacbV4iV/hSfODlRcH/E=}
$ cat>settings-security.xml<<EOF
<settingsSecurity>
<master>{40hwd7X9T1wHfRTKWlUIbHyjacbV4iV/hSfODlRcH/E=}</master>
</settingsSecurity>
EOF
#生成 docker 密码
$ mvn --encrypt-password 123456
{QdP5NFvxYhYHILE6k8tDEff+CZzWq2N3mCkPPUcljSA=}
#修改 settings.xml 中 docker 密码
<server>
<id>registry.cn-shenzhen.aliyuncs.com</id>
<username>username</username>
<password>{QdP5NFvxYhYHILE6k8tDEff+CZzWq2N3mCkPPUcljSA=}</password>
</server>
完成 dockerfile
FROM maven:3.5.4-jdk-8
COPY settings.xml /root/.m2/
COPY settings-security.xml /root/.m2/
docker build 成自己的 maven images,并修改 .gitlab-ci.yml 文件中使用的 images
私有 docker images 支持
如果 docker images 是需要login的,先生成 username:password 的 base64 值
echo -n "my_username:my_password" | base64
# Example output to copy
bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
在 Project/Group->CI/CD->Variable添加 DOCKER_AUTH_CONFIG,内容如下:
{
"auths": {
"registry.example.com": {
"auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
}
}
}
k8s helm deploy
公司项目基于 k8s helm 进行发布,这里介绍怎样基于 k8s helm 做 CD
首先需要做一个 kubectl and helm 的镜像
FROM alpine:3.4
#注意 kubectl 和 helm 的版本
ENV KUBE_LATEST_VERSION=v1.10.7
ENV HELM_VERSION=v2.9.1
ENV HELM_FILENAME=helm-${HELM_VERSION}-linux-amd64.tar.gz
RUN apk add --update ca-certificates \
&& apk add --update -t deps curl \
&& apk add --update gettext tar gzip \
&& curl -L https://storage.googleapis.com/kubernetes-release/release/${KUBE_LATEST_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl \
&& curl -L https://storage.googleapis.com/kubernetes-helm/${HELM_FILENAME} | tar xz && mv linux-amd64/helm /bin/helm && rm -rf linux-amd64 \
&& chmod +x /usr/local/bin/kubectl \
&& apk del --purge deps \
&& rm /var/cache/apk/*
运行 build 需要使用代理解决墙的问题
#HTTP_PROXY=http://192.168.1.22:1087 为 vpn 代理
docker build --build-arg HTTP_PROXY=http://192.168.1.22:1087 -t helm-kubectl:v2.9.1-1.10.7 .
kubectl 执行指令需要 k8s api 的config,找一台 k8s master 执行指令将 config 转换成 base64,创建 gitlab ci/cd 的 “kube_config” variable 值为 config base64
cat ~/.kube/config | base64
加入 deploy-test job 用于发布测试环境,考虑目前开发模式下测试环境不会更新版本号,在 deploy.yaml 的 spec/template/metadata/annotations 下添加 update/version 注解,并在 chart 配制 values
spec:
selector:
matchLabels:
app: admin
replicas: 1
revisionHistoryLimit: 3
template:
metadata:
labels:
app: admin
annotations:
update/version : ""
在 job 使用 helm upgrade –set admin.updateVersion= 可以达到重新部署测试环境的目的
deploy-test:
# Use stage test here, so the pages job may later pickup the created site.
stage: deploy-test
image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
before_script:
- mkdir -p /etc/deploy
- echo ${kube_config} | base64 -d > ${KUBECONFIG}
- kubectl config use-context kubernetes-admin@kubernetes
- helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
- helm repo add my-repo http://helm.domain.com/
- helm repo update
script:
- helm upgrade cabbage-test --reuse-values --set admin.updateVersion=`date +%s` thinker/cabbage
environment:
name: test
url: https://test.example.com
only:
- dev-4.0.1
加入 deploy-prod job 用于发布生产环境,拉 tag 时执行,使用 tag name 更新 image 的版本
deploy-prod:
# Use stage test here, so the pages job may later pickup the created site.
stage: deploy-prod
image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
before_script:
- mkdir -p /etc/deploy
#使用 kube_config variable
- echo ${kube_config} | base64 -d > /etc/deploy/config
#注意 kubernetesContextName 为 ~/.kube/config 文件中的 context name
- kubectl config use-context kubernetesContextName
#墙问题使用阿里云源
- helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
#加入需要的 helm repo
- helm repo add my-repo http://helm.domain.com/
- helm repo update
script:
# 这里只做 upgrade,CI_COMMIT_REF_NAME 是 branch or tag name
- helm upgrade cabbage-test --reuse-values --set admin.image.tag=${CI_COMMIT_REF_NAME} thinker/cabbage
environment:
name: test
url: https://prod.example.com
only:
- tags
完整例子
image: maven:3.5.4-jdk-8
services:
- docker:dind
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
KUBECONFIG: /etc/deploy/config
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
stages:
- build
- deploy-test
- deploy-prod
# mvn deploy,生成 images 并 push 到阿里云
build:jdk8:
script:
- 'mvn $MAVEN_CLI_OPTS deploy'
only:
- dev-4.0.1
stage: build
deploy-test:
# Use stage test here, so the pages job may later pickup the created site.
stage: deploy-test
image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
before_script:
- mkdir -p /etc/deploy
- echo ${kube_config} | base64 -d > ${KUBECONFIG}
- kubectl config use-context kubernetes-admin@kubernetes
- helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
- helm repo add my-repo http://helm.domain.com/
- helm repo update
script:
- helm upgrade cabbage-test --reuse-values --set admin.updateVersion=`date +%s` thinker/cabbage
environment:
name: test
url: https://test.example.com
only:
- dev-4.0.1
deploy-prod:
# Use stage test here, so the pages job may later pickup the created site.
stage: deploy-prod
image: registry.cn-shenzhen.aliyuncs.com/thinker-open/helm-kubectl:v2.9.1-1.10.7
before_script:
- mkdir -p /etc/deploy
- echo ${kube_config} | base64 -d > ${KUBECONFIG}
- kubectl config use-context kubernetes-admin@kubernetes
- helm init --client-only --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
- helm repo add my-repo http://helm.domain.com/
- helm repo update
script:
#CI_COMMIT_REF_NAME 是 branch or tag name
- helm upgrade cabbage-test --reuse-values --set admin.image.tag=${CI_COMMIT_REF_NAME} thinker/cabbage
environment:
name: test
url: https://prod.example.com
only:
- tags
总结
采用 helm 有几个问题:
- 项目第一次 install 应该由运维统一执行,如果放在 job 里做怎样处理配制文件是个问题
- 如果迭代过程中出现在配制文件项的添加和更新要怎么处理
- helm chart 文件的编写与项目开发怎么协调