前言
一直以来公司的开发、测试及生产环境都基于实体机,CI/CD通过Jenkins
完成。
最近公司的运维工程师离职了,新的还未觅得。另外,公司的业务正朝着多线方向发展,未来计划采用基于SeviceMesh
的微服务方式部署到K8S
平台。先将环境迁移到Docker
,对于零运维经验的人,看上去是一个不错的开始。
本文假设GitLab
已成功搭建运行,若想了解如何搭建GitLab,请参考这篇文章。
1. GitLab CI/CD工作流
先来看一张官网的图:
图1.1 GitLab CI/CD流程图
说明:
- GitLab CI/CD的PIPELINE是由一系列
stage
构成的,如图中CI PIPELINE的BUILD
,UNIT TEST
和INTEGRATION TESTS
; - 每个
stage
又包含一系列任务,如INTEGRATION TESTS
包含了3个任务; - 默认上一个
stage
的所有任务都成功执行,才会执行下一个stage
中的任务(可自定义执行规则); - 系统默认设置了3个
stage
:build
,test
和deploy
(可自定义,示例见下面配置文件); - 主要配置都由项目根目录下的
.gitlab-ci.yml
设定;
再来看一下GitLab的执行过程:
图1.2 GitLab执行过程图
说明:
- 每个项目根目录都有一个
.gitlab-ci.yml
配置文件; - 配置文件的主要内容包括:
- 定义一系列任务;
- 设置任务在哪个
stage
执行; - 设置任务应该由哪个
GitLab Runner
负责执行; - 设置
GitLab Runner
应该使用什么执行环境执行该任务,如某个docker镜像; - 设置任务依赖的
git
分支; - 设置任务的触发条件,如代码提交或手工触发;
-
GitLab Runner
需要在使用前先在GitLab注册:- 一般每个
GitLab Runner
都是相互独立的服务器或虚拟机,如本地办公室的开发服务器、云端的测试服务器、专门用于打包构建app的黑苹果电脑、专门用于某个项目的服务器等; -
GitLab Runner
根据任务配置,为任务准备执行环境,如shell
,docker
,k8s
等; -
GitLab Runner
注册时可以设置一到多个tag
; -
GitLab
通过配置文件中任务设置tag
,调度相应的GitLab Runner
运行任务; - 若多个
GitLab Runner
匹配执行条件,系统会随机选择一个; - 若没有相匹配的
GitLab Runner
,或所有匹配的GitLab Runner
都在忙,则任务会处于等待状态; -
GitLab Runner
可设置同时执行任务的数量;
- 一般每个
2. 安装、注册GitLab Runner
- 本示例使用
Docker
运行GitLab Runner
; - 安装完后还需要在GitLab里注册,才能使用;
- 本示例采用
alpine-10.7.2
;
示例脚本如下:
docker run --detach \
--name gitlab-runner \
--restart always \
--volume /opt/data/gitlab-runner/config:/etc/gitlab-runner \ # 配置文件
--volume /var/run/docker.sock:/var/run/docker.sock \ # 支持dind(Docker in Docker, 在Docker中构建Docker镜像)
gitlab/gitlab-runner:alpine-v10.7.2
GitLab Runner跑起来之后,运行以下脚本完成注册。详情参考这里。
docker exec -it gitlab-runner gitlab-runner register \
--name shared-runner \ # 给GitLab Runner起个名
--url "https://gitlab.com/" \ # GitLab服务器地址
--registration-token "PROJECT_REGISTRATION_TOKEN" \ # GitLab注册Token,可在GitLab管理界面获得
--description "ruby-2.5" \ # GitLab Runner的一些描述
--tag-list nodejs,java,ruby \ # 给GitLab Runner打上标签,配置文件可根据标签指定某个Runner来执行任务
--run-untagged true \ # 是否可以运行未指定标签的任务
--locked false \ # 是否锁定到某个项目
--executor "docker" \ # 任务执行环境
--docker-volumes /opt/data/ws:/share:rw \ # 使用docker执行环境时,自动挂载的目录(可选)
--docker-image ruby:2.5 # 使用docker执行环境时,设置默认执行镜像
说明:
- 任务执行环境:每种环境支持的功能有所区别。详情参考这里。
- 自动挂载目录:根据需求自行决定是否需要,一些通用的脚本和工具可放在这里。
注册完成后可以GitLab管理界面看到注册成功的GitLab Runner,如下图所示:
图2.1 GitLab Runner 列表
同时,在/opt/data/gitlab-runner/config/
目录下,可以找到config.toml
配置文件:
concurrent = 1 # 任务并发数
check_interval = 0
[[runners]]
name = "rails builder"
url = "https://gitlab.com/"
token = "PROJECT_REGISTRATION_TOKEN"
executor = "docker"
clone_url = "https://gitlab.com/"
[runners.docker]
tls_verify = false
image = "ruby:2.5"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/opt/data/ws:/share:rw"]
shm_size = 0
[runners.cache]
3. 定义.gitlab-ci.yml
# 重新定义stages,可选,也可以使用默认的;
stages:
- compile
- build
- deploy
# 将一些通用设置抽出来;
.general: &general
only:
- dev # 设置任务依赖的 git 分支
when: manual # 设置手工触发
tags:
- ror # 设置哪个GitLab Runner来执行任务
image: gitlab.com/builder:ror-v1 # 设置任务的执行环境,这里为docker镜像
script: # 设置任务具体内容,依次列出shell脚本
- /share/script/$CI_JOB_NAME.sh
# 编译任务,任务名称可任意设置
compile:
<<: *general # 引用通用设置
stage: compile # 设置任务在哪个stage执行
artifacts: # 任务执行完毕后,哪些内容需要打包,供下载或给下一个任务使用
expire_in: 12h # 过期时间,过期后自动删除打包内容
paths:
- public/assets/ # rails项目编译后的assets
- public/packs/ # rails项目中用到了react,这是编译后的react内容
- .bundle/ # bundle install后的配置文件 < 修订:新增>
# 构建docker镜像任务
build:
<<: *general
stage: build
image: docker:latest # 使用dind(Docker in Docker)的方式来构建镜像
# 部署任务
deploy:
<<: *general
stage: deploy
dependencies: [] # 依赖任务列表
配置文件提交到GitLab
后,在管理界面 -> CI/CD -> Pipelines
可以看到如下所示:
3.1 图例说明
- 每次代码提交都会产生一条新的
Pipeline
,每条都有一个编号,如图中1标注; - 点击
Pipeline
编号可以看到详情,如图3.2所示。在图中可以手工触发相应的任务; - 图中第一条已经手工触发运行过了,状态是
passed
,第二条状态是skipped
(还未手工触发); - 配置文件中设置了3个
stage
,如图中2标注; - 由于
compile
任务设置了artifacts
,图中3标注有可以点击下载的选项; - 图中3标注的左边可以手工触发任务执行;
3.2 script说明
将shell脚本依次列在script
的优缺点:
- 优点:可以将脚本变更记录纳入版本控制;
- 缺点:不方便调试,每次修改都需要先提交;
为了方便调试,示例中将所有脚本都写在单独的shell文件中。
前面提到运行GitLab Runner
时,我们配置了/opt/data/ws:/share:rw
。该配置会自动将主机的/opt/data/ws
目录自动挂载到任务运行环境(Docker)的/share
目录。因此,可以将所有shell脚本都放在本地/opt/data/ws
。
GitLab
自带了一些环境变量供配置文件使用。示例中的$CI_JOB_NAME
就是其中的一个,该变量会自动赋值为任务名称。例如,在compile
任务中,该变量为compile
,执行compile.sh
。因此,可以在主机的/opt/data/ws
目录下创建三个shell文件compile.sh
,build.sh
和deploy.sh
,分别用于执行相应的任务。
3.3 artifacts与dependencies说明
- 每个任务都可以通过
artifacts
声明,任务执行完毕后,哪些内容需要打包暂存,供下载或给下一个任务使用; - 若没有特别声明,每个任务都会默认继承前面任务的所有
artifacts
; - 可以通过
dependencies
声明,依赖哪些任务的的artifacts
; - 若不想继承任何
artifacts
,可声明dependencies
为空,如deploy
任务所示;
运行compile
任务,在任务结束时,可以看到如下关于artifacts
的信息:
...
Uploading artifacts...
public/assets/: found 631 matching files
public/packs/: found 15 matching files
Uploading artifacts to coordinator... ok id=7282 responseStatus=201 Created token=ExCbBThh
运行build
任务,在任务开始前,可以看到如下关于artifacts
的信息:
Downloading artifacts for compile (7282)...
Downloading artifacts from coordinator... ok id=7282 responseStatus=200 OK token=ExCbBThh
...
4. 构建Rails编译环境
- 将编译环境和运行环境分开,主要是想得到一个小而干净的镜像;
- 使用
ubuntu 18.04
作为编译环境,默认可安装ruby 2.5
; - 安装编译工具包需要配置时区,因此顺道安装设置了时区;
- 安装
nodejs
和yarn
(开发用到两者了);
FROM ubuntu:18.04
MAINTAINER jacky.zhang <chenghaoz@gmail.com>
# 安装并配置ruby、bundler
RUN apt update && \
apt install -y ruby && \
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ && \
gem install bundler --no-rdoc --no-ri && \
bundle config mirror.https://rubygems.org https://gems.ruby-china.com
ENV DEBIAN_FRONTEND=noninteractive # 避免设置时区有交互,打断安装过程
# 安装必备软件包(根据业务要求裁剪),并设置时区
RUN apt-get install -y build-essential libpq-dev libmysqlclient-dev imagemagick ghostscript apt-transport-https curl git ruby-dev tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
# 安装并配置nodejs、yarn
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && \
apt-get install -y nodejs yarn && \
sh -c 'echo https://registry.npm.taobao.org > ~/.npmrc'
5. 构建Rails运行环境
一直以来都使用mina
部署Rails服务,服务器环境为:Ubuntu + Nginx + Passenger
。该环境稳定运行了好多年,因此想继续沿用。
几点说明:
- 没有使用
ruby:2.5-alpine
来做基础镜像的原因:- 构建
Passenger
过程相对复杂,需要从源码编译; - 构建完的镜像也没小多少(也许有优化空间?);
-
ubuntu
环境相比较熟悉;
- 构建
- 设置系统时区:
上海
; - 安装
msyql
和postgresql
驱动(业务同时需要连接两个数据库); - 安装
imagemagick
支持图像处理; - 安装
nodejs
支持(应该可以去掉,未验证); - 安装
cron
定时任务服务(业务需要); -
nginx
需要单独安装,否则Pas
-
Passenger
官方安装文档中说明,需要先安装ruby
。经验证,最新Passenger
自带ruby 2.5
运行环境。若满足业务需求,可以不用单独安装ruby
; - 构建完镜像大小约
400M
,若清理一下/var/lib/apt/lists/
,还可以减掉40M
;
Dockerfile
如下:
FROM ubuntu:18.04
MAINTAINER jacky.zhang <chenghaoz@gmail.com>
ENV DEBIAN_FRONTEND=noninteractive # 避免设置时区有交互,打断安装过程
# 安装必备软件包(根据业务要求裁剪),并设置时区;
RUN apt-get update && \
apt-get install -y nginx cron imagemagick ghostscript libpq-dev libmysqlclient-dev nodejs tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
# 安装Passenger,自带ruby 2.5;
RUN apt-get install -y dirmngr gnupg && \
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 && \
apt-get install -y apt-transport-https ca-certificates && \
sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list' && \
apt-get update && \
apt-get install -y libnginx-mod-http-passenger && \
apt-get remove -y dirmngr gnupg && \
apt-get autoremove -y && \
apt-get clean
# 安装并设置bundle
RUN gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ && \
gem install bundler --no-rdoc --no-ri && \
bundle config mirror.https://rubygems.org https://gems.ruby-china.com
EXPOSE 80
# 默认nginx和cron服务不开机启动;
# ubuntu 18设置开机启动相对复杂,简单起见,就写在入口脚本里了;
ENTRYPOINT service nginx start && service cron start && tail -f /dev/null
6. 部署脚本
6.1 compile.sh
#!/bin/bash
echo 'compiling starts ...'
echo 'bundle: link and install '
# 为了避免每次都安装所有gem,将bundle缓存在公共目录;
ln -fs /share/env/bundle vendor/bundle
bundle install --deployment --clean
echo 'compile assets'
# 为了避免每次都所有安装npm包,将npm包缓存在公共目录;
# 注意:
# 这里不能使用link,否则nodejs编译会报错,或出现莫名其妙的bug;
# 具体原因应该是某些npm包的路径规则引起的;
mv /share/env/node_modules node_modules
RAILS_ENV=production bundle exec rails assets:precompile
mv node_modules /share/env/node_modules
echo 'compiling ends.'
6.2 build.sh
#!/bin/sh
echo 'building docker image starts ...'
echo 'copy bundle'
# 将缓存的bundle拷贝过来
cp -rf /share/env/bundle vendor/bundle
echo 'build start ...'
docker build -t test:latest .
echo 'remove untaged images'
# 如有必要移除未打标签的镜像
docker rmi -f $(docker images | grep none | awk '{print $3}')
echo 'building ends.'
项目根目录的Dockerfile如下:
FROM gitlab.com/passenger:latest
MAINTAINER jacky.zhang <chenghaoz@gmail.com>
# passenger 工作目录
ENV APP_ROOT=/var/www/app
RUN mkdir -p $APP_ROOT
# passenger默认使用www-data用户
COPY --chown=www-data . $APP_ROOT
WORKDIR $APP_ROOT
# 再运行一次bundle安装,会在项目根目录生成一些配置文件(可以在编译时缓存,以后优化)
# 如果用到whenver,就更新一下吧
# RUN RAILS_ENV=production bundle install --deployment && \
# RAILS_ENV=production bundle exec whenever --update-crontab
# 修订:删除RAILS_ENV=production bundle install --deployment
RUN RAILS_ENV=production bundle exec whenever --update-crontab
6.3 deploy.sh
部署过程主要通过ssh到远程服务器来完成:
- 先做备份;
- 移除旧的
docker
容器; - 用新的镜像重新部署,使用本地的配置文件,如nginx、项目的环境变量等;
- 部署完毕,根据需要运行
db:migration
,或重启sidekiq
服务等;
上述任务可以写在一个shell脚本中完成,过程相对简单这里略过;
7. 结束
本文记录了从零经验开始学习使用GitLab搭建CI/CD的一些经验,希望能帮到新入门的运维人员。
后续,正在进行rancher + k8s + istio的ServiceMesh实践,有时间话再来分享。
网友评论