# GitLab CI/CD Docker # 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. Pipeline Pipeline 相当于一个构建任务,里面可以包含多个流程,如依赖安装、编译、测试、部署等, 任何提交或者 Merge Request 的合并都可以触发 Pipeline. ``` ``` 2. Stages Stage 表示构建的阶段,即 Pipeline 中的包含的流程. 所有 Stages 按顺序执行,即当一个 Stage 完成后,下一个 Stage 才会开始. 任一 Stage 失败,后面的 Stages 将永不会执行,Pipeline 整个过程失败. 只有当所有 Stages 完成后,Pipeline 才会成功. ``` ``` 3. Jobs Job 是 Stage 中的任务. 相同 Stage 中的 Jobs 会并行执行. 任一 Job 失败,那么 Stage 失败,Pipeline 失败. 相同 Stage 中的 Jobs 都执行成功时,该 Stage 成功. ``` # Gitlab-ce 搭建 > Gitlab 支持本地部署, 支持 docker 部署, 支持 kubernetes 部署. > > 为了方便还是使用 docker 部署最简单, 使用 docker-compose 直接启动. ``` # 创建 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.cn' 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' ``` ``` # 使用 docker-compose up -d 启动服务 # 查看启动的服务 [root@localhost 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 ``` ``` 浏览器 http://gitlab.jicki.cn 访问 gitlab 首次访问会提示配置 初始密码,最少8位 ``` ![图1][1] ![图2][2] ``` # 创建一个 用户,作为后续 gitlab-ci 的项目关联 用户. ``` ![图3][3] ![图4][4] ![图5][5] ``` # 创建一个 Groups, 作为后续 整体团队关联 Project 项目. # 添加 上面创建的用户到此 Groups 中. ``` ![图6][6] ![图7][7] ``` # 创建一个 Project 项目. ``` ![图8][8] ![图9][9] ![图10][10] # 生成java项目 > 使用 https://start.spring.io/ 生成一个基于 Gradle 的java 项目 ![图11][11] ``` # 将生成的项目 上传到 Gitlab 的 TestProject 项目中 # 生成的项目 [root@localhost 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 ``` ``` # 修改 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' } ``` ``` # 在 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"; } } ``` ``` # 在 src/main/resources/templates 目录下 # 创建一个 index.html 文件, 内容如下: Title

Test Java Gradle project

``` ``` # 上传到 gitlab 中 [root@localhost testproject]# git add -A [root@localhost testproject]# git commit -m "add java project" [root@localhost testproject]# git push Username for 'http://gitlab.jicki.cn': jicki Password for 'http://jicki@gitlab.jicki.cn': 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.cn/java/testproject.git 9a25098..d95ec5f master -> master ``` ``` # Gitlab 中项目 Project 内容如下: ``` ![图12][12] # GitLab CI 配置 > Gitlab CI 的使用 需要使用 Runner 服务, Runner 主要负责CI任务的执行 > > 这里 Runner 服务使用 docker 运行 docker-compose 启动 ``` 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 ``` ``` # 启动之前需要先创建 一个空的 config.toml 文件 [root@localhost config]# touch config.toml # 启动 [root@localhost compose]# docker-compose up -d gitlab is up-to-date Creating gitlab-runner ... done # 查看服务 [root@localhost 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 ``` ``` # 查看日志 [root@localhost 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 服务里面去 ``` # 在注册 Runner 之前,我们需要在 gitlab 里面查看 Runner 的 Token # 一会我们注册的时候需要使用到这个 Token ``` ![图13][13] ``` # 进入容器注册 Runner [root@localhost 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.cn 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! ``` ``` # Runner registered successfully # 登录 gitlab 后台查看 注册的 Runner 服务 ``` ![图14][14] ``` # 配置完成以后~查看 config.toml 生成的配置文件 # 我这里增加了一些配置注释说明 # 配置信息的文档地址 https://docs.gitlab.com/runner/configuration/advanced-configuration.html [root@localhost 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.cn" 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] ``` ``` # 以上 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.cn:192.168.168.102"] concurrent = 1 check_interval = 0 [session_server] session_timeout = 1800 [[runners]] name = "TestProject" url = "http://gitlab.jicki.cn" 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.cn:192.168.168.102"] shm_size = 0 [runners.cache] [runners.cache.s3] [runners.cache.gcs] ``` ``` # 修改完配置以后, 删除容器,再重新启动既可. # gitlab-runner 容器为无状态的服务. ``` # 创建初始镜像 > 本项目语言为 java , 使用docker 运行 java 项目的时候,需要一个初始化的环境,这里预先构建一个 alpine 基于 openjdk 的初始镜像, dockerfile 如下: > > 初始镜像需要预先构建,然后上传到 image 仓库中,方便以后各node pull 下来. ``` 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"] ``` ``` # 这里将镜像构建为 jicki/openjdk:1.8-alpine [root@localhost compose]# docker build -t="jicki/openjdk:1.8-alpine" . [root@localhost 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 包以后,获取到这个 名称, 传递到这个 参数既可. ``` # 项目 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"] ``` ``` # 将项目 dockerfile 上传到 相对应的 git Project 里去 ``` # Gradle 配置修改 > java 基于 Gradle 的项目, 我们知道都有一个配置文件, build.gradle , 这里因为以上我们需要获取 项目镜像模板 中的 PROJECT_BUILD_FINALNAME 参数, 这里需要修改一下这个文件, 使 Gradle build 项目 jar 的时候将这个参数传递到env文件里去. ``` # 完整的 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/ ``` # 由于我们这里需要 build java gradle 项目, 在使用 自动化的时候, 我们还需要 用于构建 项目 jar 镜像 以及 docker build 的镜像, 镜像也需要预先创建好, 并上传到仓库中. ``` ``` # 用于 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 ``` ``` # 构建镜像 [root@localhost ~]# docker build -t="jicki/gradle:alpine" . [root@localhost ~]# 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 ``` ``` # 用于 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"] ``` ``` [root@localhost ~]# docker build -t="jicki/build:alpine" . [root@localhost ~]# 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 ``` ``` # 以下为 这个项目的一个 .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.*$/ ``` ``` # 提交 .gitlab-ci.yaml 文件 # 查看 gitlab ci 信息 ``` ![图15][15] ![图16][16] ![图17][17] ![图18][18] ![图19][19] ![图20][20] ``` # 查看生成的 项目镜像 [root@localhost testproject]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE jicki/test master-4e4fcd5c-20190620122155 f2a4e4ab1ff1 6 minutes ago 160MB ``` [1]: https://jicki.cn/img/posts/gitlab/1.png [2]: https://jicki.cn/img/posts/gitlab/2.png [3]: https://jicki.cn/img/posts/gitlab/3.png [4]: https://jicki.cn/img/posts/gitlab/4.png [5]: https://jicki.cn/img/posts/gitlab/5.png [6]: https://jicki.cn/img/posts/gitlab/6.png [7]: https://jicki.cn/img/posts/gitlab/7.png [8]: https://jicki.cn/img/posts/gitlab/8.png [9]: https://jicki.cn/img/posts/gitlab/9.png [10]: https://jicki.cn/img/posts/gitlab/10.png [11]: https://jicki.cn/img/posts/gitlab/11.png [12]: https://jicki.cn/img/posts/gitlab/12.png [13]: https://jicki.cn/img/posts/gitlab/13.png [14]: https://jicki.cn/img/posts/gitlab/14.png [15]: https://jicki.cn/img/posts/gitlab/15.png [16]: https://jicki.cn/img/posts/gitlab/16.png [17]: https://jicki.cn/img/posts/gitlab/17.png [18]: https://jicki.cn/img/posts/gitlab/18.png [19]: https://jicki.cn/img/posts/gitlab/19.png [20]: https://jicki.cn/img/posts/gitlab/20.png