?
通过一周的尝试, 终于从 0 到 1 把 gitlab-ci
弄好了, 彻底抛弃travis-ci
, 最大的坑还是墙外的东西太慢了, 总是 timeout
整个过程分为如下几步:
- 如何在一个 1 核 2G 的云服务器上搭建 gitlab: 十分钟搭建 Gitlab
- 使用
gitlab-runner
, 并选择正确的executor
- 如何构建前端镜像
- 如何构建后端镜像
- 编写
gitlab-ci.yml
, 实现一个完整的前端后分离项目的构建部署
1. 使用 gitlab-runner
gitlab-runner
跟 gitlab-ci
是连体婴, 主要为 gitlab-ci
打工, 使用镜像的安装方式如下:
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
其中挂载卷 /srv/gitlab-runner/config/config.toml
包含了所有 runner
的配置信息.
通过挂载 /var/run/docker.sock:/var/run/docker.sock
, 使得容器中的进程可以通过它与 Docker 守护进程通信
1.1 选择 Docker 作为 runner 的 executor
在启动了 gitlab-runner
容器后, 执行如下命令进入容器, 注册runner
docker exec -it gitlab-runner /bin/bash
root@492ce6ab72f9:/# gitlab-runner register
接下来需要填写的信息如下:
Please enter the gitlab-ci coordinator URL:
你的 Gitlab 地址: http(s)://gitlab.xxx.com
Please enter the gitlab-ci token for this runner:
你的 Gitlab admin/runners 页面中的 token
Please enter the gitlab-ci description for this runner:
填写描述, 无关紧要
Please enter the gitlab-ci tags for this runner (comma separated):
填写标签, 没有标签谁都可以用, 是 shared-runner, 有标签需要声明才可用, 回车就对了
Please enter the executor: docker-ssh, ssh, docker+machine, kubernetes, docker-ssh+machine, docker, parallels, shell, virtualbox:
选择你的 executor: Docker 应该是我观察到最常用的吧
Please enter the default Docker image (e.g. ruby:2.6):
选择一个默认镜像: 例如 docker:stable-alpine
不出意外, 就能在 gitlab
中看到了
1.2 为什么使用 docker 作为 executor
参考官方文档 – executor
-
shell executor
: 最简单的 executor, 所有依赖都必须手动安装- 不能保证每次构建都是相同的环境
- 不方便迁移
- 需要手动配置
-
Virtual Machine Executor
: 创建虚拟机用于构建, 如果你希望在不同的操作系统上构建, 可以选择它- 占用资源大
- 调试构建问题困难
- 迁移较不方便
-
Docker Executor
: 最佳的选择 QAQ -
Docker Machine
: Docker 的拓展, 工作方式与 Docker 类似, 不过需要额外的一些安装步骤
最后只尝试了 Docker
和Docker + Machine
, 只是 Docker + Machine
的runner
一直没有连接成功
1.3 什么是 Docker in Docker(dind)
参考官方文档 – dind
使用 dind
的背景是: 需要在容器内执行 docker 命令,
在 1.1
中注册好了一个 docker executor
之后, 只需要完成两个操作, 即可使用
- 在
gitlab-ci.yml
中添加:
imgage: docker:stable
service:
- docker:dind
# 测试
before_script:
- docker info
- 确保
config.toml
中该runner
中设置了privileged = true
1.4 可能遇见的问题: 拉取镜像缓慢
讲道理, 我没有在官方文档中找到: 当使用 docker 作为 runner executor 时, 如何设置 registry-mirror
如果使用 docker + machine
的话, 可以在 config.toml
中设置:
进过测试, 此项配置显然对 docker
无效 …
最终使用的办法是:
1.5 构建阶段一: 打包并上传前端代码
image: docker:stable
services:
- docker:dind
stages:
- build
- make_image
- deploy
cache:
untracked: true
paths:
- backend/node_modules/
- frontend/node_modules/
- frontend/dist/
upload_to_oss:
image: node:lts-alpine
stage: build
script:
- cd frontend
- npm install --registry https://registry.npm.taobao.org
- npm run build
通过在 gitlab-ci
控制台配置一些用于上传至 OSS 的环境变量, 就可以在这个阶段实现如下两个功能:
- 打包前端代码, 为构建前端镜像作准备
- 结构 webpack 插件, 在打包结束后将静态资源上传至 OSS
2. 如何构建前后端镜像
2.1 构建前端镜像
对于前后端分离的项目, 往往有一个特点: 需要通过 nginx 进行反向代理
因此, 前端镜像是以 NGINX 为核心的, 需要准备 2~3 个文件
-
dist
目录: 前端的静态资源 -
xxx.conf
: nginx 的部分配置, 例如: history 路由的处理, 端口转发
Dockerfile
如下:
FROM nginx:stable-alpine
LABEL maintainer=Caaalabash
EXPOSE 800
COPY xxx.conf /etc/nginx.conf.d
COPY dist /etc/nginx/dist/
CMD nginx -g "daemon off;"
xxx.conf
如下:
server {
listen 800;
server_name localhost;
root /etc/nginx/dist;
location / {
try_files $uri $uri/ @router;
index index.html;
}
location @router {rewrite ^.*$ /index.html last;}
# 此处直接使用 localhost 进行转发与后文采用的 network_mode 有关
location /api {proxy_pass http://localhost:3000/api;}
}
可以看到 xxx.conf
只与业务相关, 不涉及 https
的处理, 这部分应该交由宿主机的 nginx 进行处理, 示例代码如下:
宿主机 nginx 配置:
server {
listen 80;
server_name xxx.xxx.xxx;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2 default_server;
server_name xxx.xxx.xxx;
ssl on;
ssl_certificate cert/blog.pem;
ssl_certificate_key cert/blog.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# 所有请求交给前端容器处理
location / {proxy_pass http://127.0.0.1:8888;}
}
体现在 gitlab-ci.yml
中
make_fe:
stage: make_image
script:
- docker login -u="$DOCKER_USERNAME_ALI" -p="$DOCKER_PASSWORD_ALI" $DOCKER_REPOSITORY_ALI
- docker build -t $DOCKER_REPOSITORY_ALI/calabash/blog:$CI_COMMIT_SHORT_SHA-FE ./frontend
- docker push $DOCKER_REPOSITORY_ALI/calabash/blog:$CI_COMMIT_SHORT_SHA-FE
过程很简单, 登录私有镜像仓库, 构建镜像, 上传镜像
2.2. 构建后端镜像
对比前端就更更加简单了, 就是一个普通的构建 node 镜像流程~
FROM node:lts-alpine
LABEL maintainer=Caaalabash
WORKDIR /app
COPY package.json /app
RUN npm install --registry https://registry.npm.taobao.org
COPY . /app
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npm", "run", "online"]
体现在 gitlab-ci.yml
中
make_be:
stage: make_image
script:
- docker login -u="$DOCKER_USERNAME_ALI" -p="$DOCKER_PASSWORD_ALI" $DOCKER_REPOSITORY_ALI
- docker build -t $DOCKER_REPOSITORY_ALI/calabash/blog:$CI_COMMIT_SHORT_SHA-BE ./backend
- docker push $DOCKER_REPOSITORY_ALI/calabash/blog:$CI_COMMIT_SHORT_SHA-BE
3. 使用 docker-compose 在服务器上进行部署
在构建完镜像后, 就进入最后一个阶段: 部署
部署的核心是:
- 将相关文件拷贝到服务器, 比如一个启动脚本, 一个
docker-compose.yml
- 执行服务器脚本
体现在 gitlab-ci.yml
中为:
deploy_test:
image: caaalabash/alpine-ssh:latest
stage: deploy
before_script:
- echo "$SSH_PRIVATE_KEY" > id_rsa_2048
- chmod 0600 id_rsa_2048
- eval $(ssh-agent)
- ssh-add id_rsa_2048
script:
- scp -P $SSH_PORT deploy/* root@$DEPLOY_SERVER:$SERVER_FOLDER/deploy
- ssh -p $SSH_PORT root@$DEPLOY_SERVER "chmod +x $SERVER_FOLDER/deploy/startup.sh"
- ssh -p $SSH_PORT root@$DEPLOY_SERVER "$SERVER_FOLDER/deploy/startup.sh $CI_COMMIT_SHORT_SHA"
构建脚本只是: 停止 / 删除旧容器 / 镜像, docker-compose up
3.1 docker-compose.yml
简化版的配置如下:
version: "3"
services:
backend:
image: calabash/blog:${VUE_BLOG_TAG}-BE
container_name: blog-backend
# 只需要暴露前端端口
ports:
- 8888:800
network_mode: bridge
restart: unless-stopped
# 使用的 mongodb redis 服务
external_links:
- myMongoDB
- myRedis
frontend:
image: calabash/blog:${VUE_BLOG_TAG}-FE
container_name: blog-frontend
restart: unless-stopped
network_mode: "service:backend"
核心在于 frontend
中的配置:
network_mode: "service:backend"
通过这个配置, 和后端容器共用一个网络, 因此在 nginx 配置文件中, 可以直接使用 localhost
进行转发, 不过这样做 需要保证, 后端容器就绪后再启动前端容器
3.2 保证后端容器启动后才前端容器
总所周知, docker-compose
没有这个能力 TAT, 最使用的方案有两个
- 分别启动容器
- wait-for-it.sh
此处使用 wait-for-it.sh
的简化版操作
修改前端 Dockerfile
为:
FROM nginx:stable-alpine
LABEL maintainer=Caaalabash
EXPOSE 800
COPY wait-for-it.sh wait-for-it.sh
RUN chmod +x wait-for-it.sh
COPY blog.conf /etc/nginx/conf.d
COPY dist /etc/nginx/dist/
CMD ["./wait-for-it.sh"]
添加wait-for-it.sh
#!/bin/sh
while ! nc -z -v localhost 3000
do
echo "wait backend"
sleep 3
done
echo "done"
nginx -g "daemon off;"
即可
END
这波折腾虽然麻烦, 不过还是 完整的实现了一个前后端分离项目的 Gitlab-ci 配置 ,
达到了预期的目标, 虽然 gitlab-ci.yml
配置是非常好上手的, 除此之外
- 体验了一波 gitlab-ci + gitlab-runner 连体婴
- 使用私有镜像仓库!
- 实践了 wait-for-it 控制启动流程的方案
没想到试错了 90+ 次 …. 真不适合编程
仓库地址