原文地址
前言
之前已经说过后端CICD的部署流程,那么同样的套路,改为前端的环境即可,这里简单列一下各项配置文件以及坑
我们这里采用的是使用yarn/npm build构建后的dist文件夹index.html作为nginx的访问文件,而不是使用yarn serve/npm run serve生成访问接口的方式。不过整体来说如果是直接生成访问接口的话,那么各个文件配置会更类似后端的配置,自己按需调整即可。既然需要构建,那么沿袭我们这边后端CICD的构建原则,build过程自然是由gitlab完成,直接生成带有css/js/html的文件包,然后将文件包传到服务器上
这时候问题来了,需不需要将文件包放在容器内,其实从节约资源的角度来说其实不需要,但是我们又希望在尽可能的节约系统资源的情况下,保证部署流程的统一,这里提供几种解决方案
-
和后端部署一样,启动一个最小的容器,将文件包映射到平台服务器上,容器内部执行一个存续命令维持容器运行
这种的好处是所有前后端的的部署流程都是相同的,统一整齐,强迫症直呼内行,并且一般前端项目不会布很多所以这种资源消耗并不明显。但是问题是即使是最小的容器也会资源消耗,蚊子腿肉也是肉
-
在gitlab完成构建后,通过文件包传输将文件传输到服务器指定文件夹
这种的好处是资源消耗最小,只是看起来不是特别整齐,没有统一的管理/观测通道
-
折中方案,启动容器,将文件包映射到服务器上,但是不使用存续命令维持容器运行,退出就退出了
基本没有资源消耗,部署流程比较统一整齐,但是portainer界面不太好看,前端的容器会始终处于exit的状态,不是特别美观
这里大家自行选择,1,3两种配置基本一样,只有两个地方不同下文会说明。2方案就不展示了,只保留gitlab-ci的部分,然后再ci中使用命令将文件传到自己服务器即可,如果有需要注意权限问题
CICD配置
docker-compose.yml
上半部分没啥好说的,特别需要注意一下我们在这里是使用命名卷去映射容器,而不是使用已有文件夹映射容器,参考命名卷和映射已有文件夹的区别。简单来说对于空的宿主机映射卷这种方式会使用容器对于目录预先填充,之后再是相互同步操作。如果是空的已有文件夹,则不会有这个操作,直接将空文件夹覆盖容器已有文件夹,那么就会导致容器对应的映射文件夹为空,这种行为模式参考linux的挂载
version: "3.9"
services:
application-front:
build:
context: ./
dockerfile: Dockerfile
args:
READ_TOKEN: ${READ_TOKEN}
CI_PROJECT_ID: ${CI_PROJECT_ID}
CI_JOB_ID: ${CI_JOB_ID}
pull_policy: build
hostname: 'application-front'
container_name: 'application-front'
#restart on-failure
restart: always
volumes:
- your_new_vol:/usr/src/apps/dist
volumes:
www.astercasc.com:
name: your_new_vol
如果是使用方案3,我们需要将重启策略设置为 on-failure,防止容器一直重启
Dockerfile
astercass/alpine-simple:latest 这个镜像是alpine:latest进行增加了curl命令获得的,使用alpine镜像再手动添加curl命令效果相同。中间部分没啥好说的和后端的发布流程相同,都是从gitlab获取制品,再解包
这里有个关键点,我们不能在RUN命令中直接将包解压到容器映射目录,因为之前提到了,无论是哪种卷映射方式,都无法做到在启动时候将容器映射目录覆盖非空的宿主机目录/卷。如果使用正常解压方式,且使用是命名卷映射,首次由于命名卷内容为空,所以会用宿主机填充空的命名卷,所以首次是没问题的。但是在第二次启动的时候,命名卷中已经有数据了,则会将命名卷的数据覆盖到容器内,此时就更新了等于没更新
所以我们不能直接在RUN命令中去将数据写入映射的容器目录,但是我们知道,在容器构建完成之后目录内容会双向同步,故而我们需要将更新文件目录下放到CMD或者ENTRYPOINT中。可以理解为RUN命令是构建态,而最后的CMD和ENTRYPOINT是执行态,既然在构建态无法完成由内而外的文件覆盖,只能在执行态的时候将映射目录覆盖
FROM astercass/alpine-simple:latest
LABEL author="astercass@qq.com"
WORKDIR /usr/src/apps
ARG READ_TOKEN
ARG CI_PROJECT_ID
ARG CI_JOB_ID
RUN curl --location --output artifacts.zip \
--header PRIVATE-TOKEN:$READ_TOKEN \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/jobs/$CI_JOB_ID/artifacts" \
&& unzip -d ./data artifacts.zip
#CMD cp -r ./data/dist ./
CMD cp -r ./data/dist ./ top
如果是使用方案3,则不需要在最后执行top命令,直接让容器处于退出状态即可
gitlab-ci.yml
配置和后端的CI流程类似,这里就不再过多说明了,注意gitlab服务器在国外不需要改源不用画蛇添足,直接展示下代码
image: node:18.14.2-slim
stages:
- build
- release
#打印版本号和tag信息
before_script:
- echo ${CI_PROJECT_ID}
- echo ${CI_JOB_ID}
#gitlab current variables
- echo ${PORTAINER_APPLICATION_FRONT_WEB_HOOK}
#构建jar包
build:
stage: build
script:
- npm install -g yarn --force
- npm install -g @vue/cli
- yarn install
- yarn build
- export PARAM_VAR="CI_PROJECT_ID=${CI_PROJECT_ID}&CI_JOB_ID=${CI_JOB_ID}"
- echo "PARAM_VAR=${PARAM_VAR}" >> build.env
- cat build.env
except:
- tags
allow_failure: false
artifacts:
reports:
dotenv: build.env
paths:
- dist/
expire_in: 1 week
#deploy to
release:
image: astercass/alpine-simple:latest
stage: release
script:
- echo ${PORTAINER_APPLICATION_FRONT_WEB_HOOK}?${PARAM_VAR}
- curl -X POST "${PORTAINER_APPLICATION_FRONT_WEB_HOOK}?${PARAM_VAR}"
dependencies:
- build
nginx配置
由于和前端相关这里简单说下nginx配置,其他的关于https证书申请,子域名申请等,一方面和本文无关另一方面运营商流程经常变动这里就不做说明了
cd /etc/nginx
#首先备份
cp nginx.conf nginx.conf.bak
简单展示下conf的配置,这里展示较为简单的,复杂的配置自行访问官网
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
#使用用户,需要和systemctl的启动用户保持一致 这里不建议使用root 我这里简便起见了
user root;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
#将http请求重定向到https 将无二级域名的访问重定向到www的二级域名
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name astercasc.com;
rewrite ^/(.*)$ https://www.astercasc.com/$1 permanent;
}
server {
server_name test.astercasc.com;
rewrite ^/(.*)$ https://test.astercasc.com/$1 permanent;
}
server {
server_name astercasc.com;
rewrite ^/(.*)$ https://www.astercasc.com/$1 permanent;
}
server {
server_name www.astercasc.com;
rewrite ^/(.*)$ https://www.astercasc.com/$1 permanent;
}
server {
server_name api.astercasc.com;
rewrite ^/(.*)$ https://service.astercasc.com/$1 permanent;
}
# Settings for a TLS enabled server.
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name www.astercasc.com;
root /your_index_site;
ssl_certificate "/your_domin_crt_site/www.your_domin_bundle.crt";
ssl_certificate_key "/your_domin_crt_site/www.your_domin.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
#这里根据需要直接打配置的docker映射卷上
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name test.astercasc.com;
root /var/lib/docker/volumes/your_new_vol/_data;
ssl_certificate "/your_domin_crt_site/test.your_domin_bundle.crt";
ssl_certificate_key "/your_domin_crt_site/test.your_domin.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
}
#这里是直接打到后端接口上,如果使用的yarn serve或者npm run serve的方式则也使用这种配置方式
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.astercasc.com;
ssl_certificate "/your_domin_crt_site/api.your_domin_bundle.crt";
ssl_certificate_key "/your_domin_crt_site/api.your_domin.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:8000;
}
}
}