一直以来公司的开发、测试及生产环境都基于实体机,CI/CD 通过Jenkins
完成。
最近公司的运维工程师离职了,新的还未觅得。另外,公司的业务正朝着多线方向发展,未来计划采用基于SeviceMesh
的微服务方式部署到K8S
平台。先将环境迁移到Docker
,对于零运维经验的人,看上去是一个不错的开始。
本文假设GitLab
已成功搭建运行,若想了解如何搭建 GitLab,请参考这篇文章。
先来看一张官网的图:
说明:
stage
构成的,如图中 CI PIPELINE 的BUILD
,UNIT TEST
和INTEGRATION TESTS
;stage
又包含一系列任务,如INTEGRATION TESTS
包含了 3 个任务;stage
的所有任务都成功执行,才会执行下一个stage
中的任务(可自定义执行规则);stage
:build
,test
和deploy
(可自定义,见下面配置文件);.gitlab-ci.yml
设定;再来看一下 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
可设置同时执行任务的数量;Docker
运行GitLab Runner
;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,如下图所示:
同时,在/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]
# 重新定义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
# 编译任务,任务名称可任意设置
# 修订:将.bundle目录加入artifacts,build阶段就不需要再次bundle install了
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
可以看到如下所示:
Pipeline
,每条都有一个编号,如图中 1 标注;Pipeline
编号可以看到详情,如图 3.2 所示。在图中可以手工触发相应的任务;passed
,第二条状态是skipped
(还未手工触发);stage
,如图中 2 标注;compile
任务设置了artifacts
,图中 3 标注有可以点击下载的选项;将 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
,分别用于执行相应的任务。
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
...
ubuntu 18.04
作为编译环境,默认可安装ruby 2.5
;nodejs
和yarn
(开发用到两者了);FROM ubuntu:18.04
MAINTAINER jacky.zhang <[email protected]>
# 安装并配置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'
一直以来都使用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 <[email protected]>
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
#!/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.'
#!/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 <[email protected]>
# 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
部署过程主要通过 ssh 到远程服务器来完成:
docker
容器;db:migration
,或重启sidekiq
服务等;上述任务可以写在一个 shell 脚本中完成,过程相对简单这里略过;
本文记录了从零经验开始学习使用 GitLab 搭建 CI/CD 的一些经验,希望能帮到新入门的运维人员。 后续,正在进行 rancher + k8s + istio 的 ServiceMesh 实践,有时间话再来分享。