GitLab CI/CD Docker

Posted by 小炒肉 on June 20, 2019

Gitlab CI 介绍

GitLab CI 是 GitLab 为了提升其在软件开发工程中作用,完善 DevOps 理念所加入的 CI/CD 基础功能。可以便捷的融入软件开发环节中。通过 GitLab CI 可以定义完善的 CI/CD Pipeline。

GitLab CI 是默认包含在 GitLab 中的,如果代码在 GitLab 进行托管,可以很容易的进行集成

GitLab CI 的前端界面比较美观,容易被人接受

构建日志相对完整,容易追踪错误

使用 YAML 进行配置,任何人都可以很方便的使用

Gitlab 名词解释

gitlab 中的名词,我们在 .gitlab-ci.yaml 中会经常使用到

1
2
1. Pipeline 
    Pipeline 相当于一个构建任务,里面可以包含多个流程,如依赖安装、编译、测试、部署等, 任何提交或者 Merge Request 的合并都可以触发 Pipeline.
1
2
3
4
5
6
7
8
2. Stages
    Stage 表示构建的阶段,即 Pipeline 中的包含的流程.

    所有 Stages 按顺序执行,即当一个 Stage 完成后,下一个 Stage 才会开始.

    任一 Stage 失败,后面的 Stages 将永不会执行,Pipeline 整个过程失败.

    只有当所有 Stages 完成后,Pipeline 才会成功.
1
2
3
4
5
6
7
8
9
3. Jobs
    Job 是 Stage 中的任务.

    相同 Stage 中的 Jobs 会并行执行.

    任一 Job 失败,那么 Stage 失败,Pipeline 失败.

    相同 Stage 中的 Jobs 都执行成功时,该 Stage 成功.

Gitlab-ce 搭建

Gitlab 支持本地部署, 支持 docker 部署, 支持 kubernetes 部署.

为了方便还是使用 docker 部署最简单, 使用 docker-compose 直接启动.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建 gitlab 的 docker-compose.yaml 文件


version: '2'
services:
  gitlab:
    image: gitlab/gitlab-ce
    restart: always
    container_name: gitlab
    hostname: gitlab
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://gitlab.jicki.me'
    ports:
      - '80:80'
      - '443:443'
      - '8022:22'
    volumes:
      - './data/gitlab/config:/etc/gitlab'
      - './data/gitlab/logs:/var/log/gitlab'
      - './data/gitlab/data:/var/opt/gitlab'

1
2
3
4
5
6
7
8
9
10
# 使用 docker-compose up -d 启动服务


# 查看启动的服务

[[email protected] compose]# docker-compose ps
 Name        Command          State                                   Ports                             
--------------------------------------------------------------------------------------------------------
gitlab   /assets/wrapper   Up (healthy)   0.0.0.0:8022->22/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

1
2
3
浏览器 http://gitlab.jicki.me 访问 gitlab

首次访问会提示配置 初始密码,最少8位

图1 图2

1
# 创建一个 用户,作为后续 gitlab-ci 的项目关联 用户.

图3

图4

图5

1
2
3
4
# 创建一个 Groups, 作为后续 整体团队关联 Project 项目.

# 添加 上面创建的用户到此 Groups 中.

图6

图7

1
2
3
4
# 创建一个 Project 项目.



图8

图9

图10

生成java项目

使用 https://start.spring.io/ 生成一个基于 Gradle 的java 项目

图11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将生成的项目 上传到 Gitlab 的 TestProject 项目中

# 生成的项目
[[email protected] testproject]# ll
总用量 28
-rw-r--r-- 1 root root  446 6月  19 06:32 build.gradle
drwxr-xr-x 3 root root   21 6月  19 06:32 gradle
-rwxr-xr-x 1 root root 5305 6月  19 06:32 gradlew
-rw-r--r-- 1 root root 2269 6月  19 06:32 gradlew.bat
-rw-r--r-- 1 root root  338 6月  19 06:32 HELP.md
-rw-r--r-- 1 root root   11 6月  19 14:34 README.md
-rw-r--r-- 1 root root   96 6月  19 06:32 settings.gradle
drwxr-xr-x 4 root root   30 6月  19 06:32 src

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 修改 build.gradle , 主要添加 dependencies 下的几项

buildscript {
        repositories {
                mavenCentral()
        }
        dependencies {
                classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.21.RELEASE'
        }
}

plugins {
        id 'java'
}

apply plugin: 'org.springframework.boot'

group = 'java'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
        mavenCentral()
}

dependencies {
        compile 'org.springframework.boot:spring-boot-starter'
        compile 'org.springframework.boot:spring-boot-starter-web'
        compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
        testCompile 'org.springframework.boot:spring-boot-starter-test'
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#  src/main/java/java/TestProject 目录下
# 创建一个 HomeController.java 文件 , 内容如下:


package java.java.TestProject;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/")
    public String home(){
        return "index";
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 在 src/main/resources/templates 目录下
# 创建一个 index.html 文件, 内容如下:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<h1>Test Java Gradle project</h1>
</body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 上传到 gitlab 中

[[email protected] testproject]# git add -A

[[email protected] testproject]# git commit -m "add java project"

[[email protected] testproject]# git push
Username for 'http://gitlab.jicki.me': jicki
Password for 'http://[email protected]': 
Counting objects: 36, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 50.06 KiB | 0 bytes/s, done.
Total 20 (delta 4), reused 0 (delta 0)
To http://gitlab.jicki.me/java/testproject.git
   9a25098..d95ec5f  master -> master
1
2
# Gitlab 中项目 Project 内容如下:

图12

GitLab CI 配置

Gitlab CI 的使用 需要使用 Runner 服务, Runner 主要负责CI任务的执行

这里 Runner 服务使用 docker 运行 docker-compose 启动

1
2
3
4
5
6
7
8
services:
  gitlab-runner:
    container_name: gitlab-runner
    image: gitlab/gitlab-runner:alpine
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data/gitlab-runner/config/config.toml:/etc/gitlab-runner/config.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 启动之前需要先创建 一个空的 config.toml 文件

[[email protected] config]# touch config.toml

# 启动
[[email protected] compose]# docker-compose up -d
gitlab is up-to-date
Creating gitlab-runner ... done



# 查看服务
[[email protected] compose]# docker-compose ps
    Name                   Command                  State                                   Ports                             
------------------------------------------------------------------------------------------------------------------------------
gitlab          /assets/wrapper                  Up (healthy)   0.0.0.0:8022->22/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
gitlab-runner   /usr/bin/dumb-init /entryp ...   Up   
1
2
3
4
5
6
7
8
9
10
# 查看日志

[[email protected] compose]# docker logs -f gitlab-runner
Runtime platform                                    arch=amd64 os=linux pid=6 revision=ac2a293c version=11.11.2
Starting multi-runner from /etc/gitlab-runner/config.toml ...  builds=0
Running in system-mode.                            
                                                   
Configuration loaded                                builds=0
listen_address not defined, metrics & debug endpoints disabled  builds=0
[session_server].listen_address not defined, session endpoints disabled  builds=0

注册 Runner

Runner 注册以后必须要使用命令进行 激活,才能连接到gitlab-ce 服务里面去

1
2
3
4
# 在注册 Runner 之前,我们需要在 gitlab 里面查看 Runner 的 Token

# 一会我们注册的时候需要使用到这个 Token

图13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 进入容器注册 Runner


[[email protected] compose]# docker exec -it gitlab-runner gitlab-runner register

# 输出如下:

Runtime platform                                    arch=amd64 os=linux pid=19 revision=ac2a293c version=11.11.2
Running in system-mode.                            
                                                   
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://gitlab.jicki.me
Please enter the gitlab-ci token for this runner:
pr5VscoaY2fC_8WiSdc7
Please enter the gitlab-ci description for this runner:
[d1f701bae725]: TestProject
Please enter the gitlab-ci tags for this runner (comma separated):
JavaProject
Registering runner... succeeded                     runner=pr5Vscoa
Please enter the executor: parallels, shell, docker+machine, kubernetes, docker, docker-ssh, ssh, virtualbox, docker-ssh+machine:
docker
Please enter the default Docker image (e.g. ruby:2.1):
alpine
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

1
2
3
4
# Runner registered successfully

# 登录 gitlab 后台查看 注册的 Runner 服务

图14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 配置完成以后~查看 config.toml 生成的配置文件
# 我这里增加了一些配置注释说明
# 配置信息的文档地址 https://docs.gitlab.com/runner/configuration/advanced-configuration.html


[[email protected] config]# cat config.toml
// 限制同时运行多少个 jobs , 配置为0 为不限制
concurrent = 1
// 配置多就检查一下 jobs,最低为3,设置3以下,也为3.
check_interval = 0

[session_server]
  //jobs 完成以后保持 会话 的时间,默认为 1800 (30分钟).
  session_timeout = 1800

// 以下为 注册 runner 时填写的信息
[[runners]]
  name = "TestProject"
  url = "http://gitlab.jicki.me"
  token = "V5tiX7JbKmhJx2gSDDcb"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.docker]
    tls_verify = false
    image = "alpine"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 以上 config.toml 为默认生成的配置文件
# 以下为修改以后,的 config.toml
# 这里主要修改了一些 runner 中需要使用到的配置
# runners 中添加
#  // builds_dir 构建将存储在所选执行程序的上下文中的目录
#  builds_dir = "/gitlab/runner-builds"
#  // cache_dir 构建缓存的目录将存储在所选执行程序
#  cache_dir = "/gitlab/runner-cache"
#  由于我们 Runner 使用docker 运行,所以以上目录配置,需要额外增加配置 volumes 这个配置
#  volumes = ["/data/gitlab-runner:/gitlab","/var/run/docker.sock:/var/run/docker.sock","/data/gradle:/data/gradle","/data/sonar_cache:/root/.sonar"]
# extra_hosts 为额外的 dns 配置, 如果有dns 服务器就不需要
# extra_hosts = ["gitlab.jicki.me:192.168.168.102"]




concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "TestProject"
  url = "http://gitlab.jicki.me"
  token = "V5tiX7JbKmhJx2gSDDcb"
  executor = "docker"
  builds_dir = "/gitlab/runner-builds"
  cache_dir = "/gitlab/runner-cache"
  [runners.custom_build_dir]
  [runners.docker]
    tls_verify = false
    image = "alpine"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/opt/data/gitlab-runner:/gitlab","/var/run/docker.sock:/var/run/docker.sock","/opt/data/gradle:/root/.gradle","/opt/data/sonar_cache:/root/.sonar"]
    extra_hosts = ["gitlab.jicki.me:192.168.168.102"]
    shm_size = 0
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

1
2
3
4
# 修改完配置以后, 删除容器,再重新启动既可.

# gitlab-runner 容器为无状态的服务.

创建初始镜像

本项目语言为 java , 使用docker 运行 java 项目的时候,需要一个初始化的环境,这里预先构建一个 alpine 基于 openjdk 的初始镜像, dockerfile 如下:

初始镜像需要预先构建,然后上传到 image 仓库中,方便以后各node pull 下来.

1
2
3
4
5
6
7
8
9
10
FROM alpine

ENV JAVA_HOME /usr/lib/jvm/java-1.8-openjdk
ENV PATH $PATH:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin

RUN apk add --update bash curl tar wget ca-certificates unzip \
    openjdk8 font-adobe-100dpi ttf-dejavu fontconfig \
    && rm -rf /var/cache/apk/*

CMD ["bash"]
1
2
3
4
5
6
7
8
9
# 这里将镜像构建为 jicki/openjdk:1.8-alpine

[[email protected] compose]# docker build -t="jicki/openjdk:1.8-alpine" .


[[email protected] compose]# docker run --rm jicki/openjdk:1.8-alpine java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)

创建项目镜像

项目镜像按照每个项目运行参数不同会有不同,也可以创建一个基于 ARG 变量的 项目镜像模板 通过传参数 构建出不一样的项目镜像.

根据以下 镜像模板,这里 ARG 只有一个,PROJECT_BUILD_FINALNAME, 这里代表 java 项目包build 生成的 名称, 在build java 项目生成 jar 包以后,获取到这个 名称, 传递到这个 参数既可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 项目 dockerfile 如下:


FROM jicki/openjdk:1.8-alpine

ARG PROJECT_BUILD_FINALNAME

ENV TZ 'Asia/Shanghai'
ENV PROJECT_BUILD_FINALNAME ${PROJECT_BUILD_FINALNAME}


COPY build/libs/${PROJECT_BUILD_FINALNAME}.jar /${PROJECT_BUILD_FINALNAME}.jar

CMD ["bash","-c","java -jar /${PROJECT_BUILD_FINALNAME}.jar"]

1
2
# 将项目 dockerfile 上传到 相对应的 git Project 里去

Gradle 配置修改

java 基于 Gradle 的项目, 我们知道都有一个配置文件, build.gradle , 这里因为以上我们需要获取 项目镜像模板 中的 PROJECT_BUILD_FINALNAME 参数, 这里需要修改一下这个文件, 使 Gradle build 项目 jar 的时候将这个参数传递到env文件里去.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 完整的 build.gradle 如下:



buildscript {
        repositories {
                mavenCentral()
        }
        dependencies {
                classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.21.RELEASE'
        }
}

plugins {
        id 'java'
}

apply plugin: 'org.springframework.boot'

group = 'java'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
        mavenCentral()
}

dependencies {
        compile 'org.springframework.boot:spring-boot-starter'
        compile 'org.springframework.boot:spring-boot-starter-web'
        compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
        testCompile 'org.springframework.boot:spring-boot-starter-test'
}

bootRepackage {

    mainClass = 'java.java.TestProject.TestProjectApplication'
    executable = true

    doLast {
        File envFile = new File("build/tmp/PROJECT_ENV")

        println("Create ${archivesBaseName} ENV File ===> " + envFile.createNewFile())
        println("Export ${archivesBaseName} Build Version ===> ${version}")
        envFile.write("export PROJECT_BUILD_FINALNAME=${archivesBaseName}-${version}\n")

        println("Generate Docker image tag...")
        envFile.append("export BUILD_DATE=`date +%Y%m%d%H%M%S`\n")
        envFile.append("export IMAGE_NAME=jicki/test:`echo \${CI_BUILD_REF_NAME} | tr '/' '-'`-`echo \${CI_COMMIT_SHA} | cut -c1-8`-\${BUILD_DATE}\n")
        envFile.append("export LATEST_IMAGE_NAME=jicki/test:latest\n")
    }
}

以上配置 主要添加 bootRepackage 下面的所有选项, 这里如果懂 gradle 以及一点 bash 的话, 很容易就看懂以上配置.

创建Gitlab-ci 的配置

Gitlab CI 的配置文件, 为 .gitlab-ci.yml , 每个项目下面都需要一个 .gitlab-ci.yaml 做为自动化 ci 的配置文件.

官方关于 ci yaml 文档为 https://docs.gitlab.com/ee/ci/yaml/

1
# 由于我们这里需要 build java gradle 项目, 在使用 自动化的时候, 我们还需要 用于构建 项目 jar 镜像 以及 docker build 的镜像, 镜像也需要预先创建好, 并上传到仓库中.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 用于 gradle build  jar 的 dockerfile


FROM jicki/openjdk:1.8-alpine

ENV GRADLE_VERSION=4.5
ENV GRADLE_HOME=/opt/gradle
ENV GRADLE_FOLDER=/root/.gradle

# Change to tmp folder
WORKDIR /tmp

# Download and extract gradle to opt folder
RUN wget https://downloads.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip \
    && unzip gradle-${GRADLE_VERSION}-bin.zip -d /opt \
    && ln -s /opt/gradle-${GRADLE_VERSION} /opt/gradle \
    && rm -f gradle-${GRADLE_VERSION}-bin.zip \
    && ln -s /opt/gradle/bin/gradle /usr/bin/gradle \
    && apk add libstdc++

# Create .gradle folder
RUN mkdir -p $GRADLE_FOLDER

# Mark as volume
VOLUME  $GRADLE_FOLDER
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 构建镜像

[[email protected] ~]# docker build -t="jicki/gradle:alpine" .


[[email protected] ~]# docker run --rm jicki/gradle:alpine gradle -version

------------------------------------------------------------
Gradle 4.5
------------------------------------------------------------

Build time:   2018-01-24 17:04:52 UTC
Revision:     77d0ec90636f43669dc794ca17ef80dd65457bec

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.9 compiled on February 2 2017
JVM:          1.8.0_212 (IcedTea 25.212-b04)
OS:           Linux 4.4.181-1.el7.elrepo.x86_64 amd64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 用于 docker build image 的 dockerfile


FROM docker

ARG TZ="Asia/Shanghai"

ENV TZ ${TZ}

RUN apk upgrade --update \
    && apk add bash curl tzdata wget ca-certificates git \
    && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone \
    && rm -rf /var/cache/apk/*

CMD ["/bin/bash"]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[email protected] ~]# docker build -t="jicki/build:alpine" .



[[email protected] ~]# docker run --rm jicki/build:alpine docker version
Client: Docker Engine - Community
 Version:           18.09.6
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        481bc77
 Built:             Sat May  4 02:33:34 2019
 OS/Arch:           linux/amd64
 Experimental:      false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 以下为 这个项目的一个 .gitlab-ci.yaml 文件



# 调试开启
#before_script:
#  - pwd
#  - env

cache:
  key: $CI_PROJECT_NAME-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA
  paths:
    - build

stages:
  - build
  - deploy

auto-build:
  image: jicki/gradle:alpine
  stage: build
  script:
    - gradle --no-daemon clean assemble
  tags:
    - JavaProject

deploy:
  image: jicki/build:alpine
  stage: deploy
  script:
    - source build/tmp/PROJECT_ENV
    - echo "Build Docker Image ==> ${IMAGE_NAME}"
    - docker build -t ${IMAGE_NAME} --build-arg PROJECT_BUILD_FINALNAME=${PROJECT_BUILD_FINALNAME} .
#    - docker push ${IMAGE_NAME}
    - docker tag ${IMAGE_NAME} ${LATEST_IMAGE_NAME}
#    - docker push ${LATEST_IMAGE_NAME}
#    - docker rmi ${IMAGE_NAME} ${LATEST_IMAGE_NAME}
#    - kubectl --kubeconfig ${KUBE_CONFIG} set image deployment/test test=$IMAGE_NAME
  tags:
    - JavaProject
  only:
    - master
    - develop
    - /^chore.*$/
1
2
3
4
# 提交 .gitlab-ci.yaml 文件


# 查看 gitlab  ci 信息

图15

图16

图17

图18

图19

图20

1
2
3
4
5
# 查看生成的 项目镜像

[[email protected] testproject]# docker images
REPOSITORY                    TAG                              IMAGE ID            CREATED             SIZE
jicki/test                    master-4e4fcd5c-20190620122155   f2a4e4ab1ff1        6 minutes ago       160MB