From 6f23fe4d4ef96501d43839405dee6ad02e9dc69b Mon Sep 17 00:00:00 2001 From: gjmzj Date: Mon, 21 Jan 2019 23:39:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0gitlab-ci=E6=96=87=E6=A1=A3pa?= =?UTF-8?q?rt1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guide/gitlab/app.yaml.md | 88 +++++++++++++ docs/guide/gitlab/config.sh.md | 47 +++++++ docs/guide/gitlab/gitlab-ci.md | 85 ++++++++++++- docs/guide/gitlab/gitlab-ci.yml.md | 193 +++++++++++++++++++++++++++++ 4 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 docs/guide/gitlab/app.yaml.md create mode 100644 docs/guide/gitlab/config.sh.md create mode 100644 docs/guide/gitlab/gitlab-ci.yml.md diff --git a/docs/guide/gitlab/app.yaml.md b/docs/guide/gitlab/app.yaml.md new file mode 100644 index 0000000..1a17a89 --- /dev/null +++ b/docs/guide/gitlab/app.yaml.md @@ -0,0 +1,88 @@ +# 3.3 K8S 应用部署模板 app.yaml + +以下示例配置仅做参考,描述一个简单 java spring boot项目的 k8s 部署文件模板;在实际部署前,CI/CD流程中会对变量做替换。详见 [gitlab-ci.yml文件](gitlab-ci.yml.md)。 + +``` bash +--- +apiVersion: v1 +kind: Namespace +metadata: + name: PROJECT_NS +--- +apiVersion: v1 +kind: Secret +metadata: + name: harborkey1 + namespace: PROJECT_NS +data: + #待替换的变量DOCKER_KEY,参考 docs/guide/harbor.md#k8s%E4%B8%AD%E4%BD%BF%E7%94%A8harbor + .dockerconfigjson: DOCKER_KEY +type: kubernetes.io/dockerconfigjson + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: APP_NAME + namespace: PROJECT_NS +spec: + replicas: APP_REP + template: + metadata: + labels: + run: APP_NAME + spec: + containers: + - name: APP_NAME + image: ProjectImage + imagePullPolicy: Always + env: + # 设置java的时区 + - name: TZ + value: "Asia/Shanghai" + resources: + limits: + cpu: 500m + memory: 1600Mi + requests: + cpu: 200m + memory: 800Mi + ports: + - containerPort: 8080 + imagePullSecrets: + - name: harborkey1 + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + run: APP_NAME + name: APP_NAME + namespace: PROJECT_NS +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 8080 + selector: + run: APP_NAME + sessionAffinity: None + +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: APP_NAME-ingress + namespace: PROJECT_NS +spec: + rules: + - host: AppDomain + http: + paths: + - path: /AppPath + backend: + serviceName: APP_NAME + servicePort: 80 +``` + diff --git a/docs/guide/gitlab/config.sh.md b/docs/guide/gitlab/config.sh.md new file mode 100644 index 0000000..012acb6 --- /dev/null +++ b/docs/guide/gitlab/config.sh.md @@ -0,0 +1,47 @@ +# 3.2环境配置替换 config.sh + +首先应用开发人员需要整理在不同环境(测试环境/生产环境)的配置参数,并在源代码中约定好替换的名称(如db_host, db_usr);然后用户必须在项目gitlab web界面(“Settings”>"CI/CD">"Variables")配置变量;最后根据gitlab-ci.yml文件定义CI/CD执行的需要,编写如下简单变量替换shell脚本;该shell脚本分别在测试环境打包阶段(beta-build)和生产环境打包阶段(prod-build)阶段运行。 + +以下脚本仅作示例,实际应根据项目需要增加/修改需替换变量名称与对应源代码中的配置文件 + +``` bash +#!/bin/bash + +#set -o verbose +#set -o xtrace + +beta_config() { +sed -i \ + -e "s/db_host/$BETA_DB_HOST/g" \ + -e "s/db_usr/$BETA_DB_USR/g" \ + -e "s/db_pwd/$BETA_DB_PWD/g" \ + example-web/src/main/resources/config/datasource.properties # 项目源码的配置文件 +sed -i \ + -e "s/redis_host/$BETA_REDIS_HOST/g" \ + -e "s/redis_port/$BETA_REDIS_PORT/g" \ + -e "s/redis_pwd/$BETA_REDIS_PWD/g" \ + example-web/src/main/resources/config/redis.properties # 项目源码的配置文件 +} + +prod_config() { +sed -i \ + -e "s/db_host/$PROD_DB_HOST/g" \ + -e "s/db_usr/$PROD_DB_USR/g" \ + -e "s/db_pwd/$PROD_DB_PWD/g" \ + example-web/src/main/resources/config/datasource.properties +sed -i \ + -e "s/redis_host/$PROD_REDIS_HOST/g" \ + -e "s/redis_port/$PROD_REDIS_PORT/g" \ + -e "s/redis_pwd/$PROD_REDIS_PWD/g" \ + example-web/src/main/resources/config/redis.properties +} + +if [[ "$CI_JOB_STAGE" == "beta-build" ]];then + beta_config +elif [[ "$CI_JOB_STAGE" == "prod-build" ]];then + prod_config +else + echo "error: undefined CI_JOB_STAGE!" +fi +``` + diff --git a/docs/guide/gitlab/gitlab-ci.md b/docs/guide/gitlab/gitlab-ci.md index 9087c32..663db37 100644 --- a/docs/guide/gitlab/gitlab-ci.md +++ b/docs/guide/gitlab/gitlab-ci.md @@ -1,6 +1,6 @@ # gitlab CI/CD 基础 -gitlab-ci 兼容 travis ci 格式,也是最流行的 CI 工具之一;本文讲解利用 gitlab, gitlab-runner, docker, harbor, kubernetes 等流行开源工具搭建一个自动化CI/CD流水线;举的例子以简单实用为原则,没有选用 dind(docker in dockers)打包、gitlab Auto DevOps 等先进但未必成熟的方式。一个最简单的流水线如下: +gitlab-ci 兼容 travis ci 格式,也是最流行的 CI 工具之一;本文讲解利用 gitlab, gitlab-runner, docker, harbor, kubernetes 等流行开源工具搭建一个自动化CI/CD流水线;举的例子以简单实用为原则,暂时没有选用 dind(docker in dockers)打包、gitlab Auto DevOps 等方式。一个最简单的流水线如下: - 代码提交 --> 镜像构建 --> 部署测试 --> 部署生产 @@ -10,5 +10,86 @@ gitlab-ci 兼容 travis ci 格式,也是最流行的 CI 工具之一;本文 - 若干虚机运行 gitlab-runner: 运行自动化流水线 pipeline - 正常运行的容器仓库:[安装 Harbor 文档](../harbor.md) - 正常运行的 k8s 集群:可以是自建/公有云提供商 +- 了解代码管理流程 gitflow 等 -## +## 1.准备测试项目代码 + +假设你要开发一个 spring boot 项目;先登陆你的 gitlab 账号,创建项目,上传你的代码;项目根目录看起来如下: + +``` +-rw-r--r-- 1 root root 44 Jan 2 16:38 eclipse.bat +drwxr-xr-x 8 root root 4096 Jan 7 15:29 .git/ +-rw-r--r-- 1 root root 276 Jan 7 08:44 .gitignore +drwxr-xr-x 3 root root 4096 Jan 7 08:44 example-api/ +drwxr-xr-x 3 root root 4096 Jan 7 08:44 example-biz/ +drwxr-xr-x 3 root root 4096 Jan 2 16:38 example-dal/ +drwxr-xr-x 3 root root 4096 Jan 2 16:38 example-web/ +-rw-r--r-- 1 root root 54 Jan 2 16:38 install.bat +-rw-r--r-- 1 root root 10419 Jan 2 16:38 pom.xml +``` +传统做法是在本地配置好相关环境后使用 mvn 编译生成jar包,然后测试运行jar,这里我们要打包成 docker 镜像,并创建 CI/CD 流水线:如下示例,在项目根目录创建2个文件夹及里面文件 + +``` bash +dockerfiles ### 新增文件夹用来 docker 镜像打包 +└── Dockerfile # 定义 docker 镜像 +.ci ### 新增文件夹用来存放 CI/CD 相关内容 +├── app.yaml # k8s 平台的应用部署文件 +├── config.sh # 配置替换脚本 +└── gitlab-ci.yml # gitlab-ci 的主配置文件 +``` + +## 2.准备 docker 镜像描述文件 Dockerfile + +我们把 Dockerfile 放在独立目录下,java spring boot 应用可以这样写: + +``` bash +FROM openjdk:8-jdk-alpine +VOLUME /tmp +COPY *.jar app.jar # 这里 *.jar 包就是后续在cicd pipeline 过程中 mvn 生成的jar包移动到此目录 +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] +``` + +## 3.准备 CI/CD 相关脚本和文件 + +本地安装 gitlab 的同时也会安装帮助文档,非常有用,请阅读如下文档(假设你本地gitlab使用的域名`http://gitlab.test.com`) + +- gitlab-ci 基本概念 http://gitlab.test.com/help/ci/README.md +- 变量 http://gitlab.test.com/help/ci/variables/README.md + +目录`.ci`下面的三个文件`app.yaml`, `config.sh`, `gitlab-ci.yml`是互相关联的;整个流程中使用到的变量分为三种: + +- 第一种是gitlab自身预定义变量(比如项目名: CI_PROJECT_NAME,流水线ID: CI_PIPELINE_ID);无需更改; +- 第二种是在gitlab-ci.yml文件中定义的变量,一般是少量的自定义变量;按需少量改动; +- 第三种是用户可以在项目web界面配置的变量:“Settings”>"CI/CD">"Variables",本示例项目用到该类型变量举例: + +|变量|值|注解| +|:-|:-|:-| +|BETA_APP_REP|1|beta环境应用副本数| +|BETA_DB_HOST|1.1.1.1:3306|beta环境应用连接数据库主机| +|BETA_DB_PWD|xxxx|beta环境数据库连接密码| +|BETA_DB_USR|xxxx|beta环境数据库连接用户| +|BETA_REDIS_HOST|1.1.1.2|beta环境redis主机| +|BETA_REDIS_PORT|6379|beta环境redis端口| +|BETA_REDIS_PWD|xxxx|beta环境redis密码| +|BETA_HARBOR|1.1.1.3|beta环境镜像仓库地址| +|BETA_HARBOR_PWD|xxxx|beta环境镜像仓库密码| +|BETA_HARBOR_USR|xxxx|beta环境镜像仓库用户| +|PROD_APP_REP|2|prod环境应用副本数| +|PROD_DB_HOST|2.2.2.1:3306|prod环境应用连接数据库主机| +|PROD_DB_PWD|xxxx|prod环境数据库连接密码| +|PROD_DB_USR|xxxx|prod环境数据库连接用户| +|PROD_REDIS_HOST|2.2.2.2|prod环境redis主机| +|PROD_REDIS_PORT|6379|prod环境redis端口| +|PROD_REDIS_PWD|xxxx|prod环境redis密码| +|PROD_HARBOR|2.2.2.3|prod环境镜像仓库地址| +|PROD_HARBOR_PWD|xxxx|prod环境镜像仓库密码| +|PROD_HARBOR_USR|xxxx|prod环境镜像仓库用户| +|...|...|根据项目需要自行添加设置| + +掌握了以上基础知识,可以开始以下三个任务: + +- 3.1[配置 gitlab-ci.yml](gitlab-ci.yml.md), 整个CI/CD的主配置文件,定义所有的CI/CD阶段和每个阶段的任务 +- 3.2[配置 config.sh](config.sh.md),根据不同分支/环境替换不同的应用程序变量(对应上述第三种变量) +- 3.3[配置 app.yaml](app.yaml.md),K8S应用部署简单模板,替换完成后可以部署到测试/生产的K8S平台上 + +## to be continued diff --git a/docs/guide/gitlab/gitlab-ci.yml.md b/docs/guide/gitlab/gitlab-ci.yml.md new file mode 100644 index 0000000..56a0849 --- /dev/null +++ b/docs/guide/gitlab/gitlab-ci.yml.md @@ -0,0 +1,193 @@ +# 配置 gitlab-ci.yml + +项目组对应用CI/CD的背景需求及实现方案简介 + +- 应用测试环境部署在本地k8s平台,生产环境部署在阿里云上k8s平台 +- 应用的多个feature分支可以并行测试 +- 对于即将发布的release分支,本地提供封版测试环境,阿里云上提供UAT测试环境 + +以下示例配置为个人经验总结,仅供参考,可以根据自己的理解和项目需要不断优化完善;总体来说 gitlab-ci.yml 配置很丰富,基本上能够满足各种个性化的CI/CD流程需要。 + +``` bash +$ cat > .ci/gitlab-ci.yml << EOF +variables: ### 定义全局变量 http://gitlab.test.com/help/ci/variables/README.md + PROJECT_NS: '$CI_PROJECT_NAMESPACE-$CI_JOB_STAGE' # 定义项目命名空间,对应k8s的namespace + APP_NAME: '$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG' # 使用项目名和git提交信息作为应用名 + IMAGE_NAME: '$CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME:$CI_PIPELINE_ID' # 定义镜像名称 + +stages: ### 定义ci各阶段 + - beta-build # beta环境编译打包 + - beta-deploy # beta环境部署 + - beta-feature-delete # beta环境feature分支手动删除 + - prod-build # prod环境编译打包 + - prod-uat-deploy # prod-uat环境部署 + - prod-deploy # prod环境部署 + - prod-rollback # prod回滚 + +job_beta_build: + stage: beta-build # beta环境编译打包 + tags: + - build-shell # 定义带`build-shell`标签的runner可以运行该job + only: # 定义只在如下分支或者tag运行该job + - master + - develop + - /^feature.*$/ + - release + when: manual # 调试阶段可以先手动,后续可以注释掉以自动运行 + script: ### runner上运行的脚本 + - bash .ci/config.sh # 不同环境配置替换,后文详解 config.sh + - mvn clean install -Dmaven.test.skip=true -U # mvn 编译,可以去runner 虚机上手动执行编译测试 + - mv example-web/target/*.jar dockerfiles/ # 把mvn生成的xxx.jar移动到dockerfiles目录下 + - export IMAGE=`echo $IMAGE_NAME | sed 's/\//-/g'` # 转换镜像名,例:mygroup/java/example:172 >> mygroup-java-example:172 + - cd dockerfiles && docker build -t $BETA_HARBOR/example/$IMAGE . # 创建 docker 镜像 + - docker login -u $BETA_HARBOR_USR -p $BETA_HARBOR_PWD $BETA_HARBOR # 登陆到内部镜像仓库 harbor,并推送 + - docker push $BETA_HARBOR/example/$IMAGE + - docker logout $BETA_HARBOR + +job_push_beta: ### 推送到beta环境,可以推送不同分支 develop, feature-1, ...> + stage: beta-deploy # 可以做到多分支同时测试,甚至最后的release分支也要在beta封版测试 + tags: + - beta-shell # 定义带`beta-shell`标签的runner可以运行该job + only: + - master + - develop + - /^feature.*$/ + - release + when: manual # 调试阶段可以先手动,后续可以注释掉以自动运行 + variables: + BETA_EXP_Domain: '$CI_COMMIT_REF_SLUG.example.test.com' # job内部变量,指定该应用在beta环境的 ingress 域名 + script: + - export IMAGE=`echo $IMAGE_NAME | sed 's/\//-/g'` # 转换 $IMAGE_NAME 中可能的 / 字符 + - export PROJECT_NS=`echo $PROJECT_NS | sed 's/\//-/g'` # 转换命名空间中可能有的 / 字符 + # 替换beta环境的参数配置 + - sed -i "s/PROJECT_NS/$PROJECT_NS/g" .ci/app.yaml ### app.yaml 即k8s的部署模板文件,详见后面 app.yaml.md 文档,注意这里的变量有的来自> + - sed -i "s/APP_NAME/$APP_NAME/g" .ci/app.yaml # gitlab 系统变量, 有的是在项目 CI/CD 设置里面用户定义的变量 + - sed -i "s/APP_REP/$BETA_APP_REP/g" .ci/app.yaml + - sed -i "s/AppDomain/$BETA_EXP_Domain/g" .ci/app.yaml + - sed -i "s/ProjectImage/$BETA_HARBOR\/example\/$IMAGE/g" .ci/app.yaml + - sed -i "s/DOCKER_KEY/$BETA_KEY/g" .ci/app.yaml # DOCKER_KEY 为k8s平台能从镜像仓库pull所需的认证信息,详见harbor文档 + # + - mkdir -p /opt/kube/$PROJECT_NS/$APP_NAME # 在runner:beta-shell虚机本地创建应用配置目录,调试检查用 + - cp -f .ci/app.yaml /opt/kube/$PROJECT_NS/$APP_NAME + - kubectl --kubeconfig=/etc/.beta/config apply -f .ci/app.yaml # 部署应用(runner虚机上预先配置了kubectl权限执行测试k8s平台) + +job_delete_beta: ### 多测试环境并行部署在beta k8s平台,feature分支测试完毕后删除代码分支, + stage: beta-feature-delete # 同时需要删除该分支在k8s平台上的部署,可以由开发人员自行执行该job删除 + tags: + - beta-shell + only: + - /^feature.*$/ + when: manual + script: + - export PROJECT_NS=`echo $PROJECT_NS | sed 's/\//-/g'` + - kubectl --kubeconfig=/etc/.beta/config delete deploy,svc,ing $APP_NAME -n $PROJECT_NS + +job_prod_build: ### prod环境编译打包,这里prod环境我们使用阿里云上的K8S + stage: prod-build # 阿里云k8s平台上运行的uat环境和正式环境都使用本次打包镜像 + tags: + - build-shell + only: # 仅master和release分支可以执行该job + - master + - release + when: manual + script: + - bash .ci/config.sh # config.sh 会执行替换生产环境的变量 + - mvn clean install -Dmaven.test.skip=true -U # mvn 编译,可以去runner 虚机上手动执行编译测试 + - mv example-web/target/*.jar dockerfiles/ # 把mvn生成的xxx.jar移动到dockerfiles目录下 + - export IMAGE=`echo $IMAGE_NAME | sed 's/\//-/g'` + - cd dockerfiles && docker build -t $PROD_HARBOR/example/$IMAGE . + - docker login -u $PROD_HARBOR_USR -p $PROD_HARBOR_PWD $PROD_HARBOR + - docker push $PROD_HARBOR/example/$IMAGE + - docker logout $PROD_HARBOR + +job_push_prod_uat: ### 部署至阿里云uat环境 + stage: prod-uat-deploy + tags: + - prod-shell + when: manual + only: # 仅master和release分支可以执行该job + - master + - release + variables: + PROD_EXP_Domain: 'example-uat.xxxx.com' # job内部变量,指定该应用在uat环境的 ingress 域名 + script: + - export IMAGE=`echo $IMAGE_NAME | sed 's/\//-/g'` + - export PROJECT_NS=`echo $PROJECT_NS | sed 's/\//-/g'` + # 替换prod环境的参数配置 + - sed -i "s/PROJECT_NS/$PROJECT_NS/g" .ci/app.yaml + - sed -i "s/APP_NAME/$CI_PROJECT_NAME/g" .ci/app.yaml + - sed -i "s/APP_REP/1/g" .ci/app.yaml + - sed -i "s/AppDomain/$PROD_EXP_Domain/g" .ci/app.yaml + - sed -i "s/ProjectImage/$PROD_HARBOR\/example\/$IMAGE/g" .ci/app.yaml + - sed -i "s/DOCKER_KEY/$PROD_KEY/g" .ci/app.yaml + # + - mkdir -p /opt/kube/$PROJECT_NS/$APP_NAME + - cp -f .ci/app.yaml /opt/kube/$PROJECT_NS/$APP_NAME + - kubectl --kubeconfig=/etc/.aliyun/config apply -f .ci/app.yaml + +job_push_prod_release: ### 部署至阿里云正式环境 + stage: prod-deploy + tags: + - prod-shell + when: manual + only: # 仅master和release分支可以执行该job + - master + - release + variables: + PROD_EXP_Domain: 'example.xxxx.com' # 指定该应用在阿里云正式环境的 ingress 域名 + script: + - export IMAGE=`echo $IMAGE_NAME | sed 's/\//-/g'` + - export PROJECT_NS=`echo $PROJECT_NS | sed 's/\//-/g'` + # 替换prod环境的参数配置 + - sed -i "s/PROJECT_NS/$PROJECT_NS/g" .ci/app.yaml + - sed -i "s/APP_NAME/$CI_PROJECT_NAME/g" .ci/app.yaml + - sed -i "s/APP_REP/$PROD_APP_REP/g" .ci/app.yaml + - sed -i "s/AppDomain/$PROD_EXP_HOST/g" .ci/app.yaml + - sed -i "s/ProjectImage/$PROD_HARBOR\/example\/$IMAGE/g" .ci/app.yaml + - sed -i "s/DOCKER_KEY/$PROD_KEY/g" .ci/app.yaml + # + - mkdir -p /opt/kube/$PROJECT_NS/$APP_NAME + - cp -f .ci/app.yaml /opt/kube/$PROJECT_NS/$APP_NAME + - kubectl --kubeconfig=/etc/.aliyun/config apply -f .ci/app.yaml + +1/3 rollback: ### 定义生产环境回退job + stage: prod-rollback + tags: + - prod-shell + when: manual + only: + - master + - /^release.*$/ + variables: + PROJECT_NS: '$CI_PROJECT_NAMESPACE-prod-deploy' # 定义job内变量覆盖全局变量设置 + script: + - kubectl --kubeconfig=/etc/.aliyun/config -n $PROJECT_NS rollout undo deployment $CI_PROJECT_NAME --to-revision=1 + +2/3 rollback: + stage: prod-rollback + tags: + - prod-shell + when: manual + only: + - master + - /^release.*$/ + variables: + PROJECT_NS: '$CI_PROJECT_NAMESPACE-prod-deploy' # 定义job内变量覆盖全局变量设置 + script: + - kubectl --kubeconfig=/etc/.aliyun/config -n $PROJECT_NS rollout undo deployment $CI_PROJECT_NAME --to-revision=2 + +3/3 rollback: + stage: prod-rollback + tags: + - prod-shell + when: manual + only: + - master + - /^release.*$/ + variables: + PROJECT_NS: '$CI_PROJECT_NAMESPACE-prod-deploy' # 定义job内变量覆盖全局变量设置 + script: + - kubectl --kubeconfig=/etc/.aliyun/config -n $PROJECT_NS rollout undo deployment $CI_PROJECT_NAME --to-revision=3 +EOF +``` +