最近想把公司基于Jenkins的自动化构建修改到GitLab上,主要原因是在Jenkins上没有做权限控制,大家使用同一个账号,造成不同项目组的源码泄漏问题;另外还有一个使用Jenkins的独立服务器,感觉还是资源浪费了点。
配置GitLab的runner
从gitlab上获取Runner注册的token。有三种方式
- 按项目
项目
->设置
->CI/CD
->Runner
- 按分组
分组
->设置
->CI/CD
->Runner
- 全局
管理中心
->概览
->Runner
运行Runner
- 从gitee上下载对应的的helm包,地址
- 修改values文件的
gitlabUrl
、runnerRegistrationToken
、tags
- 执行以下命令
helm package .
helm install --namespace gitlab --name gitlab-runner *.tgz
说明:
gitlabUrl
就是你gitlab私服地址runnerRegistrationToken
就是从第1步获取的runner注册的token值tags
标识这个runner,stage的job就是根据该tag选择对应的runner的Chart.yaml
定义该helm的基本信息,注意里面的name
字段必须和当前目录的名称一致templates/pvc.yaml
定义了一个Dynamic Provisioning的PVC,使用的storageClassName
是阿里云的alicloud-disk-efficiency
(我们的k8s是阿里云的容器服务),它会自动创建对应的PV,并创建一个云盘实例。这个可以用来处理多个Job之间的缓存文件templates/configmap.yaml
文件的runners.kubernetes.volumes.pvc
定义了PVC的名称,即上一步定义的PVC名称,如果有修改注意同步
至此,一个gitlab-runner应该就可以注册到对应的gitlab server上去了。
配置环境变量
对于在构建过程使用到比较私密的信息,应该直接配置到gitlab server的环境变量上,这里我们主要配置一下三个参数:
REGISTRY_PASSWORD
Harbor私服的密码REGISTRY_USERNAME
Harbor私服的用户名KUBE_CONFIG
kubectl执行所需的账号、证书等信息,该字符串可以使用以下命令获取
echo $(cat ~/.kube/config | base64) | tr -d " "
这里的环境变量是配置在group下的
CI/CD
页面的环境变量
对于以下容器的配置为了纯手工配置,更好的方式应该是使用Dockerfile进行编写。这里只是为了更好的说明基础镜像的制作过程。
配置Node容器
Node容器主要用于编译前端项目,一般主要使用yarn
下载依赖,npm
编译打包。所以Node容器需要包含这两个命令。
$ docker pull node //拉取最新的node镜像
$ docker run -it --rm --name node node /bin/sh //运行node镜像,并且进入
$ yarn config set registry https://registry.npm.taobao.org //配置yarn的源为淘宝源
$ yarn config list //查看配置
--------------------------------
info yarn config
{
'version-tag-prefix': 'v',
'version-git-tag': true,
'version-commit-hooks': true,
'version-git-sign': false,
'version-git-message': 'v%s',
'init-version': '1.0.0',
'init-license': 'MIT',
'save-prefix': '^',
'bin-links': true,
'ignore-scripts': false,
'ignore-optional': false,
registry: 'https://registry.yarnpkg.com',
'strict-ssl': true,
'user-agent': 'yarn/1.16.0 npm/? node/v12.5.0 linux x64',
version: '1.16.0'
}
info npm config
{
version: '1.16.0'
}
Done in 0.07s.
--------------------------------
$ docker commit node harbor_url/tools/node-taobao //另外打开一个窗口,提交修改后的node镜像
$ docker push harbor_url/tools/node-taobao //推送镜像到Harbor私服
配置Java容器
Java容器主要用于编译Java项目,主要用到JDK
和MAVEN
$ docker pull alpine // 拉取最新的alpine镜像
$ docker run -it --rm --name java alpine //进入镜像
$ mkdir -p /opt/java // 创建java目录
$ mkdir -p /opt/maven //创建maven目录
$ docker cp jdk-8u211-linux-x64.tar.gz java:/opt/java/ //从主机上拷贝JDK到容器内部
$ docker cp apache-maven-3.6.1-bin.tar.gz java:/opt/maven/ //从主机上拷贝MAVEN到容器内部
/opt/maven $ tar -xzvf apache-maven-3.6.1-bin.tar.gz //在容器内解压MAVEN
/opt/maven $ rm -rf apache-maven-3.6.1-bin.tar.gz //删除MAVEN压缩包
/opt/java $ tar -xzvf jdk-8u211-linux-x64.tar.gz //在容器内解压JDK
/opt/java $ rm -rf jdk-8u211-linux-x64.tar.gz //删除JDK压缩包
$ vi /etc/profile //配置环境变量
--------------------------------
export JAVA_HOME=/opt/java/jdk1.8.0_211
export M2_HOME=/opt/maven/apache-maven-3.6.1
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$JAVA_HOME/bin:$M2_HOME/bin
--------------------------------
//Java是基于GUN Standard C library(glibc),Alpine是基于MUSL libc(mini libc),所以需要安装glibc库
// 参考地址:https://blog.csdn.net/Dannyvon/article/details/80092834
$ echo http://mirrors.ustc.edu.cn/alpine/v3.10/main > /etc/apk/repositories
$ echo http://mirrors.ustc.edu.cn/alpine/v3.10/community >> /etc/apk/repositories
$ apk update
$ apk --no-cache add ca-certificates
$ wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.27-r0/glibc-2.27-r0.apk
$ wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
$ apk add glibc-2.27-r0.apk // 至此glibc库安装完毕
$ source /etc/profile //生效环境变量
$ java -version //查看JAVA版本
--------------------------------
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
--------------------------------
$ mvn -v //查看MAVEN版本
--------------------------------
Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-04T19:00:29Z)
Maven home: /opt/maven/apache-maven-3.6.1
Java version: 1.8.0_211, vendor: Oracle Corporation, runtime: /opt/java/jdk1.8.0_211/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "4.9.125-linuxkit", arch: "amd64", family: "unix"
--------------------------------
$ docker commit java harbor_url/tools/java:1.8 //在另外一个窗口,提交镜像
$ docker push harbor_url/tools/java:1.8 //将镜像推送到Harbor私服
注意
source /etc/profile
这个命令只是当前有效,在提交镜像后,使用镜像重新运行容器还需要在执行一次该命令。具体如何永久生效,还不知道。如果大佬知道可以告知下。
配置Curl&Git容器
该容器只是适应我们公司的状况,我们公司的前端打包其实并不需要Node容器。前端打包过程是在本地打包编译后生成dist目录下的文件,然后压缩上传到内网的oss上。接着在Jenkins上执行脚本,其实是从内网的oss下载并解压,然后根据Dockerfile在制作业务镜像。这个过程就需要使用到Curl和Git命令。整个过程是为了解决线上打包环境可能跟开发本地不一致,以及前端打包需要下载依赖和编译耗时的问题,通过这样的一个流程,前端每次构建的时间就非常短,几秒钟就能搞定。
$ docker run -it --rm --name curl-git alpine
$ echo http://mirrors.ustc.edu.cn/alpine/v3.10/main > /etc/apk/repositories
$ echo http://mirrors.ustc.edu.cn/alpine/v3.10/community >> /etc/apk/repositories
$ apk update
$ apk add curl
$ apk add git
$ curl -V
--------------------------------
curl 7.61.1 (x86_64-alpine-linux-musl) libcurl/7.61.1 LibreSSL/2.6.5 zlib/1.2.11 libssh2/1.8.2
Release-Date: 2018-09-05
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile NTLM NTLM_WB SSL libz UnixSockets HTTPS-proxy
--------------------------------
$ git version
--------------------------------
git version 2.15.3
--------------------------------
$ docker commit curl-git harbor_url/tools/curl-git //在另外一个窗口,提交镜像
$ docker push harbor_url/tools/curl-git //将镜像推送到Harbor私服
配置kubectl容器
kubectl容器是用于部署应用到k8s集群,需要用到config
的配置信息。但是这个信息一般比较私密,不会直接打到容器里面,容器里面只会放一个kubectl客户端,然后具体的配置文件在运行期间在放到~/.kube/config
文件内。
$ docker run -it --rm --name kubectl alpine //运行并且进入容器
$ docker cp kubectl kubectl:/usr/bin/ //拷贝kubectl客户端到容器内部,kubectl客户端可以直接在k8s的master节点找到,或者直接在github上下载到对应版本的客户端
$ chmod +x /usr/bin/kubectl //设置为可执行文件
$ kubectl version //查看版本,因为没有配置服务的证书,所以服务端的信息打印不出来
--------------------------------
Client Version: version.Info{Major:"1", Minor:"12+", GitVersion:"v1.12.6-aliyun.1", GitCommit:"4304b26", GitTreeState:"", BuildDate:"2019-04-08T08:50:29Z", GoVersion:"go1.10.8", Compiler:"gc", Platform:"linux/amd64"}
The connection to the server localhost:8080 was refused - did you specify the right host or port?
--------------------------------
$ docker commit kubectl harbor_url/tools/kubectl:1.12.6 //在另外一个窗口,提交镜像
$ docker push harbor_url/tools/kubectl:1.12.6 //将镜像推送到Harbor私服
配置.gitlab-ci.yml
# 如果各个stage没有使用镜像,则使用默认镜像
image: $HARBOR_URL/tools/alpine
stages:
- build
- deploy
# 全局变量定义
variables:
# 镜像名,tag默认使用pipeline_id
IMAGE_NAME: <harbor_url>/<image_path>/<image_name>
# 定义应用名称,即deployment_name、container_name、service_name等
APP_NAME: <application_name>
# 应用端口,一般应用端口和service的端口一致
APP_PORT: 80
# 定义命名空间,需要根据不同的打包命令替换为真实命名空间
NAMESPACE: dev
# HARBOR_URL
HARBOR_URL: <harbor_url>
docker_build_job:
# 包含curl 和 git 工具的镜像
image: $HARBOR_URL/tools/curl-git
stage: build
# 定义只有dev分支push或者merge事件触发Job
only:
refs:
- dev
tags:
- <runner_tag>
# 使用dind模式,连接到一个有启动docker的容器
services:
- $HARBOR_URL/tools/docker-dind:18.09.7
variables:
# docker daemon 启动的参数
DOCKER_DRIVER: overlay
DOCKER_HOST: tcp://localhost:2375
# 该 stage 执行的脚本命令
# 1. 执行构建脚本build.sh
# 2. 登录Harbor私服
# 3. 根据Dockerfile构建image
# 4. push image 到Harbor
script:
# build.sh脚本为公司内部脚本,根据后面的参数配置不同的环境
# 主要流程是下载开发打包好的压缩文件,然后解压,接着根据不同环境替换变量
- sh ./build.sh -b development
- docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD $HARBOR_URL
- docker build -t $IMAGE_NAME:$CI_PIPELINE_ID .
- docker push $IMAGE_NAME:$CI_PIPELINE_ID
k8s_deploy_job:
# 包含kubectl 工具的镜像
image: $HARBOR_URL/tools/kubectl:1.12.6
stage: deploy
only:
refs:
- dev
tags:
- <runner_tag>
# 该 stage 执行的脚本命令
# 1. 创建/etc/deploy/config文件
# 2. 将k8s的证书信息写入/etc/deploy/config
# 3. 替换deployment.yaml文件中的各个变量
# 4. 部署到k8s集群环境中
script:
- mkdir -p ~/.kube
- touch ~/.kube/config
# KUBE_CONFIG这个环境变量是在gitlab server中配置的
- echo $KUBE_CONFIG | base64 -d > ~/.kube/config
# 对deployment.yaml进行对应的变量替换
- sed -i "s?IMAGE_TAG?$CI_PIPELINE_ID?g" deployment.yaml
- sed -i "s?IMAGE_NAME?$IMAGE_NAME?g" deployment.yaml
- sed -i "s?APP_NAME?$APP_NAME?g" deployment.yaml
- sed -i "s?NAMESPACE?$NAMESPACE?g" deployment.yaml
- sed -i "s?APP_PORT?$APP_PORT?g" deployment.yaml
- kubectl apply -f deployment.yaml
这个.gitlab-ci.yml文件少了基于node和java镜像的配置。不过都是大同小异,就不在复述。对于node镜像打包后一般会生成dist目录,这个时候可以加个步骤把dist目录压缩,然后定义
artifacts
,这样当前的stage
执行完后就会上传该压缩包到gitlab-server。接着下一个stage
就会自动下载这个压缩包,这样我们就可以解压这个压缩包,然后再根据Dockerfile进行打包了,同样是使用dind的模式。对于java镜像同样可以使用这个原理,mvn编译打包出一个jar包或者war包,然后传递到下一个stage
,再进行构建镜像;不过如果使用maven的docker插件的话,那就不用分两个stage
了,直接在java的那个镜像加个services
的定义,这样就可以使用mvn docker:build docker:push
命令了。不过要注意,使用maven的docker插件,镜像是定义在pom.xml
文件的,这个需要和外部的.gitlab-ci.yml文件定义的镜像名称同步
node镜像部分定义
node_build_job:
image: $HARBOR_URL/tools/node-taobao
stage: package
only:
refs:
- dev
tags:
- <runner_tag>
script:
# 下载依赖
- yarn
# 编译打包
- npm run build --qa
# 压缩dist目录
- tar -czvf dist.tar.gz dist/
# 定义artifacts,会上传到gitlab-server
artifacts:
paths:
- dist.tar.gz
java镜像部分定义
java_build_job:
image: $HARBOR_URL/tools/java:1.8
stage: package
only:
refs:
- dev
# services定义,使用dind模式,其实就是通过link指令把docker容器链接到java镜像,使得java镜像可以使用docker命令
services:
- $HARBOR_URL/tools/docker-dind:18.09.7
variables:
DOCKER_DRIVER: overlay
DOCKER_HOST: tcp://localhost:2375
tags:
- <runner_tag>
script:
# 重新使得JAVA_HOME、M2_HOME环境变量生效
- source /etc/profile
- mvn -P$NAMESPACE clean package
- cd <module_dir>
- mvn -P$NAMESPACE docker:build docker:push
配置deployment.yaml
deployment.yaml文件中包含2部分,k8s的deployment
对象和service
对象。
apiVersion: apps/v1beta2
kind: Deployment
metadata:
labels:
app: APP_NAME
name: APP_NAME
namespace: NAMESPACE
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: APP_NAME
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: APP_NAME
spec:
affinity: {}
containers:
- image: 'IMAGE_NAME:IMAGE_TAG'
imagePullPolicy: Always
name: APP_NAME
# 前端项目,使用的是nginx基础镜像,一般使用内存都比较低
resources:
limits:
cpu: '1'
memory: 64Mi
requests:
cpu: '0'
memory: 32Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: APP_NAME
namespace: NAMESPACE
spec:
ports:
- port: APP_PORT
protocol: TCP
targetPort: APP_PORT
selector:
app: APP_NAME
sessionAffinity: None
type: ClusterIP
结束
至此,一个完整的基于gitlab的CICD流程可以跑起来了。因为是配合k8s运行的,在整个搭建的过程还是坎坷。例如使用javaj镜像但是需要运行docker命令,services
那块的定义如果不去看文档就稀里糊涂的;然后前端的yarn
和mvn install
等命令都会涉及到从公网下载依赖包,这些依赖包如何缓存才能使得下一次构建可以直接使用,这个就涉及到k8s的PV和PVC的相关概念和使用。
另外,对于.gitlab-ci.yml的变量还是有写死的内容,例如namespace,还需要另外一个脚本根据打包命令来替换对应的变量。还有待优化。
这个整套流程跑下来,感觉又学到了一些东西。
一个吃饱了努力减肥的小胖子。。。