Caaalabash / blog-frontend

Vue3+Vite+ElementPlus实现的个人博客,包含GitlabCI、Docker、Nginx、docker-compose相关配置
https://blog.calabash.top
MIT License
23 stars 8 forks source link

从零到一实现前后端分离项目的gitlab-ci流程 #66

Open Caaalabash opened 4 years ago

Caaalabash commented 4 years ago

?

通过一周的尝试, 终于从0到1把gitlab-ci弄好了, 彻底抛弃travis-ci, 最大的坑还是墙外的东西太慢了, 总是timeout

整个过程分为如下几步:

1. 使用gitlab-runner

gitlab-runnergitlab-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

最后只尝试了DockerDocker + Machine, 只是Docker + Machinerunner一直没有连接成功

1.3 什么是Docker in Docker(dind)

参考官方文档 - dind

使用dind的背景是: 需要在容器内执行docker命令,

1.1中注册好了一个docker executor之后, 只需要完成两个操作, 即可使用

imgage: docker:stable

service:
  - docker:dind

# 测试
before_script:
  - docker info

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的环境变量, 就可以在这个阶段实现如下两个功能:

2. 如何构建前后端镜像

2.1 构建前端镜像

对于前后端分离的项目, 往往有一个特点: 需要通过nginx进行反向代理

因此, 前端镜像是以NGINX为核心的, 需要准备2~3个文件

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在服务器上进行部署

在构建完镜像后, 就进入最后一个阶段: 部署

部署的核心是:

体现在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的简化版操作

修改前端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配置是非常好上手的, 除此之外

没想到试错了90+次....真不适合编程

仓库地址

yimng commented 4 years ago

第1.4步,加用/var/run/docker.sock:/var/run/docker.sock之后还需要什么配置吗? 楼主能否详细说下。

Caaalabash commented 4 years ago

@yimng 第1.4步,加用/var/run/docker.sock:/var/run/docker.sock之后还需要什么配置吗? 楼主能否详细说下。

1.4步骤的主要目的是为了设置镜像源;国内机器docker不设置镜像源的话,下载是龟速的,而gitlab-runner的docker executor又没地方设置源,所以把宿主机的docker.sock挂载进去,相当于使用宿主机器的docker,要问额外的配置的话,大概就是“给宿主机的docker添加镜像源了”

yimng commented 4 years ago

@Caaalabash

@yimng 第1.4步,加用/var/run/docker.sock:/var/run/docker.sock之后还需要什么配置吗? 楼主能否详细说下。

1.4步骤的主要目的是为了设置镜像源;国内机器docker不设置镜像源的话,下载是龟速的,而gitlab-runner的docker executor又没地方设置源,所以把宿主机的docker.sock挂载进去,相当于使用宿主机器的docker,要问额外的配置的话,大概就是“给宿主机的docker添加镜像源了”


image: docker:19.03.8

services:

variables: DOCKER_HOST: tcp://docker:2376 DOCKER_TLS_CERTDIR: "/certs" DOCKER_DRIVER: overlay2
DOCKER_BUILDKIT: 1


在command里不知道能不能加上registry, 这个问题也困扰了我好久,我用的是自己用Nexus搭建的docker registry(1.1.1.79:8445). 还有个问题想请教,如果我有多个项目(前后端分离,包括java, vue, python),每个项目都是一个git repo, 是每个项目都建个.gitlab-ci.yml来触发ci,还是单起个项目专门用来ci呢?
Caaalabash commented 4 years ago

@yimng

@Caaalabash

@yimng 第1.4步,加用/var/run/docker.sock:/var/run/docker.sock之后还需要什么配置吗? 楼主能否详细说下。

1.4步骤的主要目的是为了设置镜像源;国内机器docker不设置镜像源的话,下载是龟速的,而gitlab-runner的docker executor又没地方设置源,所以把宿主机的docker.sock挂载进去,相当于使用宿主机器的docker,要问额外的配置的话,大概就是“给宿主机的docker添加镜像源了”


image: docker:19.03.8

services:

  • name: docker:19.03.8-dind command: ["--insecure-registry=1.1.1.79:8445"]

variables: DOCKER_HOST: tcp://docker:2376 DOCKER_TLS_CERTDIR: "/certs" DOCKER_DRIVER: overlay2
DOCKER_BUILDKIT: 1


在command里不知道能不能加上registry, 这个问题也困扰了我好久,我用的是自己用Nexus搭建的docker registry(1.1.1.79:8445). 还有个问题想请教,如果我有多个项目(前后端分离,包括java, vue, python),每个项目都是一个git repo, 是每个项目都建个.gitlab-ci.yml来触发ci,还是单起个项目专门用来ci呢?

似乎可以单起一个仓库,将前端/后端/其他的repo用git submodule引入,拉代码的时候git clone --recursive即可(没这么实践过,但是应该是可行的)

yimng commented 4 years ago

``

@yimng

@Caaalabash

@yimng 第1.4步,加用/var/run/docker.sock:/var/run/docker.sock之后还需要什么配置吗? 楼主能否详细说下。

1.4步骤的主要目的是为了设置镜像源;国内机器docker不设置镜像源的话,下载是龟速的,而gitlab-runner的docker executor又没地方设置源,所以把宿主机的docker.sock挂载进去,相当于使用宿主机器的docker,要问额外的配置的话,大概就是“给宿主机的docker添加镜像源了”

image: docker:19.03.8

services:
  - name: docker:19.03.8-dind
    command: ["--insecure-registry=1.1.1.79:8445"]

variables:
  DOCKER_HOST: tcp://docker:2376
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_DRIVER: overlay2  
  DOCKER_BUILDKIT: 1

在command里不知道能不能加上registry, 这个问题也困扰了我好久,我用的是自己用Nexus搭建的docker registry(1.1.1.79:8445). 还有个问题想请教,如果我有多个项目(前后端分离,包括java, vue, python),每个项目都是一个git repo, 是每个项目都建个.gitlab-ci.yml来触发ci,还是单起个项目专门用来ci呢?

似乎可以单起一个仓库,将前端/后端/其他的repo用git submodule引入,拉代码的时候git clone --recursive即可(没这么实践过,但是应该是可行的)

好的,多谢,有时间我试一试