Gitlab Runner发布服务

gitlab runner是gitlab cicd的执行器,对于提交的代码通常都会进行打包然后发布,而runner通常和生产的机器不在同一个机器上,所以本文将讨论runner如何将构建好的服务发布到生产的服务器上。

runner的执行器有多种类型,常用的有shell和docker

  • shell executor: 在runner所在的环境执行任务,也就是说,如果runner运行在linux上,就在linux中运行任务;如果运行在docker容器中,那么就在容器中执行任务。
  • docker executor: 启动一个新docker容器执行任务,使得构建任务的环境隔离。

获取访问生产服务器的权限

以docker executor为例,runner收到任务后启动了一个新的容器,那么在这个新的容器默认是没有ssh密钥对的,所以我们要做的是让这个容器拥有访问生产服务器的ssh密钥对。

方式1:拥有密钥对的镜像

我们可以制作一个拥有访问生产服务器密钥对的镜像,runner收到任务的时候就通过该镜像启动容器。

方式2:容器运行时导入密钥对

在 GitLab CI/CD 中使用 SSH 密钥

在admin > > setting > CICD > Variables位置导入私钥文件

导入后在执行脚本前,将私钥的环境变量导入到容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
deploy:
stage: deploy
image: docker:latest
before_script:
## Install ssh-agent if not already installed
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
## Run ssh-agent
- eval $(ssh-agent -s)
##
## Give the right permissions, otherwise ssh-add will refuse to add files
## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store
##
- chmod 400 "$SSH_PRIVATE_KEY"
- ssh-add "$SSH_PRIVATE_KEY"
## Create the SSH directory and give it the right permissions
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh

这里和传统的将密钥对放到.ssh目录不一样,这里使用了一个软件ssh-agent,然后通过ssh-add将密钥导入到内存中,这种方式是为了避免私钥持久化,因为当前使用的是runner的docker executor所以任务执行完成容器就销毁了,所以这种方式和持久化的方式都可以。

发布服务

有了访问生产服务器的权限后,就可以开始发布服务了

方式1:ssh直连发布

这种方式没什么需要特别注意的地方

方式2:通过docker context远程启动docker compose发布

通过docker context,可以在一台机器上控制多台机器的docker。

创建生产的docker context

1
docker context create production --docker "host=ssh://root@xx.xx.xx.xx"

启动生产环境的docker compose

1
docker-compose --context production up -d

包含权限的容器

我想runner在执行的时候启动一个包含连接生产环境权限的容器,也就是这个镜像默认包含ssh生产服务器的权限。

错误的方式

我的想法是写一个setup.yml,该文件配置了读取环境变量并加载到ssh-agent中的逻辑,然后各项目的.gitlab-ci.yml可以include这个setup.yml,这样就可以实现各个项目能够公用一份连接生产环境的setup.yml。

编写setup.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 每个任务执行前都会执行下面这段代码
default:
before_script:
## Install ssh-agent if not already installed
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
## Run ssh-agent
- eval $(ssh-agent -s)
##
## Give the right permissions, otherwise ssh-add will refuse to add files
## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store
##
- chmod 400 "$SSH_PRIVATE_KEY"
- ssh-add "$SSH_PRIVATE_KEY"
## Create the SSH directory and give it the right permissions
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
## Verify the SSH host key
- ssh-keyscan xx.xx.xx.xx >> ~/.ssh/known_hosts

新建Dockerfile,镜像用于构建自己的服务并包含有连接生产环境的权限,在Dockerfile中包含上面setup.yml

1
2
3
4
5
6
7
FROM docker:latest

# 复制 setup.yml 到容器的 /config/ci 目录
COPY setup.yml /config/ci/setup.yml

# 创建生产环境的docker context
RUN docker context create production --docker "host=ssh://root@xx.xx.xx.xx"

然后在各个需要发布到生产服务器的项目引入公共逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
stages:
- build
- deploy
- publish

# 引入公共逻辑
include:
- local: '/config/ci/setup.yml'

deploy:
stage: deploy
## 包含setup.yml的镜像
image: docker-executor:latest
script:
## Execute your deployment commands
- ssh root@xx.xx.xx.xx "cd /app/hexo && git pull && docker-compose pull && docker-compose up -d --force-recreate"
rules:
- changes:
- Dockerfile
- docker-compose.yml
- entrypoint.sh
- config/*
- blog/**/*
- .gitlab-ci.yml

但是发现include总是找不到/config/ci/setup.yml,原来.gitlab-ci.yml无法找到所在目录之外的文件。

可行的方式

  • 将setup.yml文件放到各个项目中,比较冗余
  • 在每个任务中使用before_script代替include,在before_script执行初始化脚本,执行的脚本就没有在项目目录中的限制
  • 构建包含密钥对的容器,而不是从环境变量中加载

使用脚本获取ssh

编写脚本setup.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
## Install ssh-agent if not already installed
command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )
## Run ssh-agent
eval $(ssh-agent -s)
##
## Give the right permissions, otherwise ssh-add will refuse to add files
## Add the SSH key stored in SSH_PRIVATE_KEY file type CI/CD variable to the agent store
##
chmod 400 "$SSH_PRIVATE_KEY"
ssh-add "$SSH_PRIVATE_KEY"
## Create the SSH directory and give it the right permissions
mkdir -p ~/.ssh
chmod 700 ~/.ssh
## Verify the SSH host key
ssh-keyscan xx.xx.xx.xx >> ~/.ssh/known_hosts

构建包含该脚本的镜像的Dockerfile并构建为docker-executor

1
2
3
4
5
6
7
8
FROM docker:latest

# 复制 setup.yml 到容器的 /config/ci 目录
COPY ./setup.sh /app/
RUN chmod +x /app/setup.sh

# docker context
RUN docker context create production --docker "host=ssh://root@10.0.24.9"

构建镜像

1
docker build -t docker-executor:latest .

修改runner的配置文件config.toml,修改拉取策略为if-not-present,
否则每次都会从dockerhub拉取,然而该镜像并没有上传dockerhub

1
2
3
4
5
6
7
8
9
10
11
12
[runners.docker]
tls_verify = false
image = "docker-executor:latest"
pull_policy = ["if-not-present"]
allowed_pull_policies = ["always", "if-not-present"]
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
shm_size = 0
network_mtu = 0

在项目的.gitlab-ci.yml添加before_script,这里可以不设镜像会默认使用runner配置的镜像,也可以显式指定。

1
2
3
4
5
6
7
deploy:
stage: deploy
image:
name: docker-executor:latest
pull_policy: if-not-present
before_script:
- source /app/setup.sh