自动化部署(Jenkins, Ansible,Bash)
使用 Jenkins,Ansible,Bash 自动化部署的流程。
前言
前端项目中有这么几个不直接服务于 UI 的文件、目录,主要就是用于自动部署的:
- /ansible
- /scripts
- build.sh
- Dockerfile
- entrypoint.sh
- nginx.conf
- .gitlab-ci.yml
示例中对脚本进行了优化,所以会跟源码稍有不同。
PS: 代码相关的内容会分为代码详细注释和总结执行结果。
入口
开发环境一般用的是 gitlab ci/cd,查看 .gitlab-ci.yml
中 deploy 阶段可以看到其获取代码后执行的是 ./scripts/deploy.sh 脚本。
./scripts/deploy.sh 脚本内容和 Jenkins 部署流程中的脚本类似,所以下面主要展开看 Jenkins 的部署流程。
有权限的话可以查看 Jenkins 的配置,下面仅截取关键步骤。
第一步是先将指定分支的代码克隆到指定位置,然后执行以下脚本:
sh "sed -i 's/10.12.78.47/hostName.com/g' ./nginx.conf"
sh "./build.sh && cd ansible && ansible-playbook -i '10.xx.xx.xx,10.xx.xx.xx' display.yml -e role=prado_fe --extra-vars \"ListenPort=9000\""
这两条命令就是部署的全过程,下面会详细展开说这两条命令的执行。
第一行命令
sh "sed -i 's/10.12.78.47/hostName.com/g' ./nginx.conf"
涉及的关键字:sed
-> stream editor-i
-> 直接修改读取的文件内容
语法:sed -i ‘s/原字符串/新字符串/g’ file (g 在正则中为全局替换)
所以第一行的命令翻译过来就是 nginx.conf 文件中的 10.12.78.47 全部替换为 hostName.com
查看项目中的 nginx.config 可以知道修改的是 proxy_pass 的内容:
server {
location /api {
proxy_pass http://10.12.78.47; // 改为 http://hostName.com
}
}
总结
修改 nginx.conf
中 proxy_pass 字段的内容。
第二行命令
sh "./build.sh && cd ansible && ansible-playbook ..."
- 执行 build.sh 脚本
- 进入到 ansible 目录执行 ansible-playbook
下面分别展开看这两步的执行流程
build.sh
#!/bin/bash
# 定义变量 IMAGES_NAME
IMAGES_NAME="prado_ui:latest"
# 打印环境变量 ENV, 可以在 Jenkins 的构建参数中看到 ENV 为 live
echo $ENV
# 使用当前目录下的 Dockerfile 构建定制的镜像
docker build --build-arg ENV -t $IMAGES_NAME .
# $? 是 shell 变量,表示最后一次执行命令的状态,0为成功,非0为失败
# -ne -> 不等于
if [ $? -ne 0 ]; then
echo "build dot ui images failed"
exit 1
# fi 为 if 语句的结尾
fi
# 构建完镜像后,将镜像保存到 ./ansible/role/prado_fe/files 目录下
cd ./ansible/role/prado_fe/files && docker save $IMAGES_NAME > prado_ui.tar && cd ..
总结
执行 docker build 构建镜像,生成镜像后将镜像保存在 ansible/role/prado_fe/files 目录下,文件名为 prado_ui.tar。
执行 docker build 默认会读取目录下的 DOCKERFILE 进行构建,所以接着查看 Dockerfile 了解构建的镜像内容。
Dockerfile
有两个 From,多阶段编译,只保留最后的一个阶段的结果
# 阶段一
# 基础镜像包含node。后续阶段可以通过 builder 获取该阶段的结果
FROM xxx.com/prado-user-images/node:latest AS builder
# 指定后续 COPY 和 RUN 的工作路径
WORKDIR /usr/src/app
# 将项目的依赖文件复制到包中
COPY package.json yarn.lock ./
# 安装依赖
RUN yarn
# 将跟 Dockerfile 同目录的所有内容(即项目源码)复制到包中
COPY . ./
# ARG ENV="dev" 可以被命令中的 --build-arg ENV 覆盖为 live,仅在DOCKERFILE中生效
ARG ENV="dev"
# 构建目标文件
RUN yarn build:$ENV
# 清除超大的依赖,这步是必须的。虽然阶段1并不会保留到最终镜像内,但是docker会创建这一步build的缓存,硬盘空间很快就会不够用
RUN rm -rf ./node_modules/
# 阶段二
# 基础镜像包含nginx
FROM hostName.com/prado-user-images/nginx:alpine
# 将阶段一中 /usr/src/app/dist 目录下的文件(也就是构建产物)复制到当前镜像的 /usr/share/nginx/html 路径下
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
# 将 nginx.conf 和 entrypoint.sh 复制到镜像中
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh ./
# 定义环境变量 DOMAIN 和 PORT,容器运行时生效
ENV DOMAIN "test-hostName.com"
ENV PORT 80
# 容器启动时执行 sh ./entrypoint.sh
CMD ["sh","./entrypoint.sh"]
总结
最终的镜像主要就是包含 ngnix 和构建后的网站的静态文件。
build 命令执行完后,就是 ansible 命令相关的内容了。
ansible
ansible 自动化运维的框架,简单理解就是远程连接需要部署的主机并发送相关的命令,仅介绍他的执行和执行结果。
ansible-playbook -i '10.xx.xx.xx,10.xx.xx.xx' display.yml -e role=prado_fe --extra-vars \"ListenPort=9000\"
执行 display.yml 所定义的流程
-i hosts
-i 指定需要部署的主机 ip,默认会读取 ansible/hosts 文件,此处是直接指定两台主机的 ip
-e –extra-vars
都是对 yml 文件和模板文件传参,分别传了 role=prado_fe 和 ListenPort=9000
ansible/display.yml
---
- name: Installing prado_agent
gather_facts: no # 是否收集各机器的信息
hosts: all
become: yes # 需要root权限
become_user: root # 需要的特权用户
roles:
- "{{ role }}" # role 被替换为 prado_fe
#- { role: prado_agent }
#- { role: change_config }
display.yml 总结
最后的执行的 roles 为 prado_fe,查看对应文件夹 ansible/role/prado_fe
下的文件执行
roles/prado_fe 目录解释
- files 存放需要 copy 的文件,经过上面的 build.sh,会在该目录下生成 prado_ui.tar
- tasks 用户存放一系列任务
- handlers 空 无视
- template 存放此 Role 需要使用的 jinjis2 模板文件
查看任务的的入口 tasks/main.yml
ansible/role/prado_fe/tasks/main.yml
---
- name: get tar
# 将 prado_ui.tar 复制到远程主机的 /tmp/ 目录下
copy:
src: "{{ role_path }}/files/prado_ui.tar"
dest: "/tmp/"
- name: get deploy_sh
# 获取模板 deploy.j2,替换文件中的 {{ ListenPort }} 变量为9000后,保存到远程主机 /tmp/ 目录下的deploy_ui.sh文件中
template:
src: deploy.j2
dest: /tmp/deploy_ui.sh
- name: start ui
shell:
# 在 tmp目录下执行命令
chdir: "/tmp/"
# 给予所有用户执行 deploy_ui.sh 的权限,并执行 deploy_ui.sh
cmd: chmod a+x deploy_ui.sh && ./deploy_ui.sh
# 保存执行结果到 checkStarted 中,暂无用
register: checkStarted
main.yml 总结
将 prado_ui.tar 和 deploy_ui.sh(deploy.j2)复制到远程主机的 tmp 目录下并执行 deploy_ui.sh
所以接下来就是 deploy_ui.sh 的执行
ansible/role/prado_fe/templates/deploy.j2
deploy.j2 -> deploy.sh
#!/bin/bash
NAME="prado_ui"
BACK_NAME="prado_ui_backup"
IMAGES_NAME="prado_ui:latest"
IMAGES_NAME_BACKUP="prado_ui:backup"
# 先做一通操作,清除和备份上一次的运行容器、镜像,可用作回滚
# 停止运行中的容器 prado_ui
docker stop $NAME
# 删除容器 prado_ui
docker rm $NAME
# 删除镜像 prado_ui:backup
docker image rm $IMAGES_NAME_BACKUP
# 给 prado_ui:latest 打上 backup 标签
docker tag $IMAGES_NAME $IMAGES_NAME_BACKUP
# 删除镜像 prado_ui:latest
docker image rm $IMAGES_NAME
# 加载镜像 prado_ui.tar,也就是我们构建的那个镜像
docker load < prado_ui.tar
# 运行镜像 prado_ui:latest
# -itd -it 提供可交互的伪终端,容器启动时需要执行shell脚本(entrypoint.sh),-d 保持在后台执行
# --name 指定容器名字为 prado_ui
# -p 将本地主机的 ListenPort 映射到容器的 80 端口,ListenPort在上一步知道被替换为9000
docker run -itd --name $NAME -p {{ ListenPort }}:80 $IMAGES_NAME
在 DOCKERFILE 一节可以知道,容器启动后会执行脚本 entrypoint.sh
entrypoint.sh
#!/usr/bin/env bash
# 侯勋所有bash命令返回的code如果不是0,脚本立即退出
set -e
# 定义DOMAIN和PORT,:=指定默认值,DOCKERFILE中已经定义为了 ENV DOMAIN "test-hostName.com" 和 ENV PORT 80
DOMAIN="${DOMAIN:-test-hostName.com}"
PORT="${PORT:-80}"
# 将nginx配置中的 port 改成 80,domain 改成 test-hostName.com
sed -i "s#{{port}}#${PORT}#g" /etc/nginx/conf.d/default.conf
sed -i "s#{{domain}}#${DOMAIN}#g" /etc/nginx/conf.d/default.conf
# 启动ngnix服务,ngnix默认为后台模式启动,使用daemon off改为前台进程,避免跟随脚本执行进程退出,导致容器退出
exec nginx -g "daemon off;"
最终启动的 ngnix 的配置为
server {
listen 80;
server_name test-hostName.com;
access_log /dev/null;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}
location /api {
proxy_pass http://hostName.com;
}
}
根据前面的配置,访问主机的 9000 端口,就能访问到这个容器中的 ngnix 在 80 端口的服务了
PS: 从域名到服务整个代理过程暂时黑盒,没搜到文档