自动化部署(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: 从域名到服务整个代理过程暂时黑盒,没搜到文档