什么是CICD
CI:持续集成(CONTINUOUS INTEGRATION)
CI,Continuous Integration,持续集成,是软件开发过程中一个非常重要的环节。在互联网敏捷开发的过程中,持续集成通常用来进行日常编译和自动化测试,来保证及时发现提交的问题,避免影响项目进度。
持续集成主要包含以下步骤:
- 自动化构建Continuous Build
- 自动化测试Continuous Test
- 自动化集成Continuous Intergration
持续集成过程中很重视自动化测试验证结果,以保障所有的提交在合并主线之后的质量问题,对可能出现的一些问题进行预警。
CD:持续部署(CONTINUOUS DEPLOYMENT)
持续交付(Continuous Delivery, CD)是一种软件工程的手段,让软件在短周期内产出,确保软件随时可以被可靠地发布。
其目的在于更快、更频繁地构建、测试以及发布软件。通过加强对生产环境的应用进行渐进式更新,这种手段可以降低交付变更的成本与风险。
本文使用 Gitlab CI/CD 和 Docker 实践,本文用到的docker命令可查看这里
Gitlab CI/CD
Gitlab 是开源的 devops 平台,集成了 Gitlab CI/CD 功能,通过 gitlab runner 完成CICD:
- 开发者在commit代码或者提交merge request会自动触发CI/CD流程
- 流程开始后,会主动读取项目根目录下的 .gitlab_ci.yml 文件,获取构建镜像,构建步骤,构建命令等,并运行一个CI pipeline(一个pipeline通常分为三个阶段:build,test,deploy),即会执行一系列任务,如用eslint校验代码规范,单元测试等。
- 根据.gitlab_ci.yml中配置的stage中的tags,选择对应的gitlab runner,根据配置的image启动容器,并在该容器中执行stage中的构建命令
Gitlab CI/CD 中pipeline(流水线),stage(阶段),job(任务)之间的关系为:pipeline 包含了若干个 stage,stage 包含了多个 job,job 是流水线中最小的单位,这些任务是在 gitlab runner 中运行。
gitlab还集成了sentry sdk,可通过连接sentry进行监控,还可以接入kubernets集群等。有兴趣可进入gitlab官网查看详细内容。
建议实践前先了解 linux 命令,shell 脚本,docker 基本命令,yml 语法,gitlab yml脚本等储备知识,还需要有一台服务器搭建gitlab,本文使用阿里云服务器搭建,可选择带有docker环境的服务器,这里使用的服务器是 centos 系统。
远程连接服务器
搭建前需要远程连接阿里云服务器,这里使用远程连接的工具是 xshell 7,安装后根据步骤连接服务器即可。
安装gitlab
这里使用的服务器是 centos 系统,不同版本会有兼容问题,如 centos 8。
centos8安装gitlab会很多问题,包括但不限于以下问题:
- centos8 Error: yum Failed to download metadata for repo 'AppStream'
- Error: Failed to download metadata for repo 'epel'
解决方案:降级至centos7.8就没问题了。
开始前可先自行了解下 docker
常用命令。
安装 gitlab 命令如下,sudo 为获取超级管理权限,如果没有域名可不配置 hostname,ip 也能访问:
sudo docker run --detach \
--hostname gitlab.moon.top \
--publish 443:443 --publish 80:80 --publish 222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
docker run 参数:
-
detach 后台运行,不加参数的话直接是打印日志,不是在后台运行
-
hostname 服务器访问域名或ip
-
publish 端口映射,[宿主机端口]:[容器端口],此处 222 是宿主机端口,22 是容器端口
-
name 容器名称
-
restart 重启方式,比如遇到服务器宕机或是停电了会自动重启。
-
volume 加载一个数据卷,是目录映射,[服务器本地目录]:[容器内目录],此处镜像为gitlab镜像,这里的目录依次对应的是gitlab配置,gitlab日志,项目代码,做了目录映射后就可以把数据保存到本地而不是保存到容器中,因为保存到容器里时,若容器停止或是删除,数据将会丢失。
-
gitlab/gitlab-ce:latest 镜像名称
运行后会返回容器id,这个返回是异步的,gitlab启动很慢,可通过 docker logs gitlab
命令查看日志,加 -f
参数可查看实时日志
安装遇到的问题
(1) 服务器公网 ip 无法访问 gitlab
启动成功后服务器 ip 无法访问 gitlab,访问需要在服务器开放端口
暴露给外部的端口需要打开对应的安全组设置,就需要添加 80 端口的安全组配置
设置后若还是不行可跳转此处寻找别的方案
(2) 重置root密码
gitlab 14 以后无法在页面中重置 root 默认密码,创建 gitlab 用户需要使用 root 账户审批才能正常登录,本文直接使用 root 账号创建仓库
有两种方式重置密码
- 获取 root 默认密码
sudo docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
输入上述命令后可返回 root
默认密码,然后在 gitlab 网站上使用用户名 root
和默认密码登录并修改。
此方法第一次使用可以,删了之前的 gitlab 容器后,重新 docker run 安装新的 gitlab 后不知为何不行,望知道原因的朋友告知。
- 命令行重置密码
// 进入gitlab容器中, gitlab是容器名字
docker exec -it gitlab bash
// 使用root特权登录到服务器。使用以下命令启动Ruby on Rails控制台
gitlab-rails console -e production
控制台加载完毕后如下图
等待控制台加载完毕,有多种找到用户的方法,您可以搜索电子邮件或用户名:
user = User.where(id: 1).first
或者
user = User.find_by(email: 'admin@example.com')
然后更改密码,必须同时更改密码和 password_confirmation
才能使其正常工作,密码长度需为8位,最后保存:
user.password = [密码]
user.password_confirmation = [密码]
user.save! // 保存密码
exit // 退出ruby控制台
保存密码的命令不要忘了 !
哦
密码设置成功后会返回true,此时还需输入exit退出控制台,才能登录root账户,否则gitlab网站登录root账户后会显示设置密码失败的页面,设置成功后如下图:
使用域名访问 gitlab 服务配置
若需要用域名访问 gitlab,可在阿里云注册域名并配置域名解析,阿里云还可以买二手域名,本文用的是ip,所以这里不做赘述。
设置项目域名或IP
如果发现项目的域名或ip设置错了,可以修改配置文件调整回来。
- 在服务器通过 vim 编辑
gitlab.rb
文件
vim /etc/gitlab/gitlab.rb
如果找不到上述文件是因为容器映射到服务器的文件没创建成功,可直接修改容器内的 gitlab.rb
文件
vim /srv/gitlab/config/gitlab.rb
- 按下
i
键进入编辑模式,找到external_url
,去掉注释,修改成对应域名 or IP
external_url 'http://120.79.76.143'
- 按下
esc
键,输入:wq
命令保存退出,并重启 gitlab 容器
docker stop gitlab
docker restart gitlab
安装 gitlab-runner
部署完gitlab服务后还需安装注册gitlab runner才能运行cicd任务。
sudo 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
安装后可使用 docker ps 查看容器是否运行成功,且可以看到 gitlab runner 启动后没有端口号是因为本身不对外提供服务,只是一个软件
注册gitlab runner
安装后还需注册gitlab runner,即在 gitlab runner 生成一个容器指向 gitlab,需要在注册 gitlab runner 时配置 gitlab 地址和 token
,在 gitlab 平台上登录root账户可以找到 url 和 token 信息。
-
使用 admin 的token注册的runner是共享runner
-
使用项目中的token注册的runner是项目私有runner
sudo docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \
--non-interactive \
--executor "docker" \
--docker-image alpine:latest \
--url "[ip/域名]/" \
--registration-token "[token]" \
--description "for-vite" \
--tag-list "vite" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"
参数: executor 可取值有docker/shell/k8s docker-image 基础dorker基础镜像 url 访问地址 registration-token 注册token tag-list 注册runner的标签,yml文件中指定运行的runner参数值
编写 .gitlab-ci.yml 文件
先介绍常用的任务关键词,详细解析可看官网文档:
- image 指定 docker 镜像作为运行环境,可在全局/任务中定义,经常用的镜像有node,java,python,docker,使用npm install命令需设置node镜像,否则找不到npm命令。
- stages 全局定义阶段
- cache 将当前工作环境目录中的一些文件,文件夹存储起来,用于在各个任务初始化的时候恢复,比如当 安装依赖和构建项目分开两个job来运行,则需要把依赖放在缓存中,否则构建时会报错
- stage 定义任务内的阶段,必须从全局阶段中选任务如果有相同的阶段会并发执行,官方默认有五个stages,如果没有指定stage,默认是 test 的stage:
- .pre
- build
- test
- deploy
- .post
- tags 指定流水线用的 runner,只能定义在任务上,取值范围在项目中可见的 runner tags 中。
- script 执行shell脚本
- retry 重试机制,容易失败的任务可以使用这个字段,比如npm install
- only/expect 限定当前任务能否被执行
- when 实现在发生故障或尽管发生故障时仍能运行的作业
TIP: 任务的执行顺序不是按写的顺序执行,而是按阶段定义的顺序执行。gitlab-runner默认每条流水线只有一个任务并发执行,为减少内存消耗,若需要多个任务并发执行需另行配置。
先看个最简单的.gitlab-ci.yml文件,这个pipeline有三个阶段:eslint -> build -> deploy,eslint 和 deploy 后面再填充脚本:
image: node:alpine
stages:
- eslint
- build
- deploy
eslint:
stage: eslint
tags:
- vite
script:
- echo "eslint pass!"
build:
stage: build
tags:
- vite
script:
- npm install
- npm run build
- echo "build success!"
deploy:
stage: deploy
script:
- echo "deploy success!"
使用docker镜像部署项目
提供目录卷使用官方的配置 vim /srv/gitlab-runner/config/config.toml,否则会报错
volumes = ["/cache","/usr/bin/docker:/usr/bin/docker", "/var/run/docker.sock:/var/run/docker.sock"]
写好后按 Esc
键退出编辑模式,并输入保存命令 :wq!
退出
部署配置
还需编写Dockerfile文件,使用node基础镜像构建下载依赖包,然后构建项目,构建后再换成nginx镜像再次构建,最后把dist目录放到nginx目录中
FROM node:latest
WORKDIR /app
COPY package.json .
RUN npm install --registry=http://registry.npm.taobao.org
COPY . .
RUN npm run build
FROM nginx:latest
COPY --from=builder /app/dist /usr/share/nginx/html
在yml配置文件中配置docker命令,构建镜像,如果之前有运行该容器则删除,最后运行容器:
deploy:
stage: deploy
image: docker
tags:
- vite
script:
- docker build -t folive .
- if [ $(docker ps -aq --filter name=mylive-container) ]; then docker rm -f mylive-container;fi
- docker run -d -p 8001:80 --name mylive-container folive
- echo 'deploy docker image success. visit http://120.79.76.143:8001'
部署成功后可通过链接访问页面
部署后若有问题可以进入容器查看nginx默认配置或是部署文件
docker exec -it [容器ID] bash
alpine容器使用以下命令进入容器
docker exec -it 9f7dc4dd2c53 /bin/sh
查看配置文件 cat /etc/nginx/conf.d/default.conf
镜像体积优化
docker构建镜像需要注意的是镜像的大小,Dockerfile 中的每条指令都会为镜像添加一层,需要记住在继续下一层之前清理不需要的任何制品。要编写一个真正高效的 Dockerfile,通常需要使用 shell 技巧和其他逻辑来保持层尽可能小,并确保每一层都具有前一层所需的制品,而没有其他任何东西。
查看Docker的磁盘使用情况
docker system df
使用multistage构建
当需要依次使用多个镜像构建时容易造成用到的所有镜像都构建到最终的制品中,因此可以使用多阶段构建来解决这个问题。
简单的说,通过多阶段构建,可以FROM在 Dockerfile 中使用多个语句,每条FROM指令都可以使用不同的基础,它们中的每一条都开始了构建的新阶段。从而可以选择性地将制品从一个阶段复制到另一个阶段,从而最后的镜像中只保留需要的东西。
有兴趣可查看官网详细内容。前面配置的Dockerfile文件使用的就是多阶段构建的方式。
避免安装不必要的包
减小体积,减少构建时间。如前端应用使用 npm install --production 只装生产环境所依赖的包,还能减少安装包的时间。
减小容器耦合
如一个web应用将会包含三个部分,web 服务,数据库与缓存。把他们解耦到多个容器中,方便横向扩展。如果你需要网络通信,则可以将他们至于一个网络下。
减少镜像层数
只有 RUN, COPY, ADD 会创建层数, 其它指令不会增加镜像的体积,并尽可能使用多阶段构建
错误的方法安装依赖,这将增加镜像层数
RUN yum install -y node
RUN yum install -y python
RUN yum install -y go
使用以下方法安装依赖可以减少镜像层数
RUN yum install -y node python go
充分利用构建缓存
在镜像的构建过程中 docker 会遍历 Dockerfile 文件中的所有指令,顺序执行。对于每一条指令,docker 都会在缓存中查找是否已存在可重用的镜像,否则会创建一个新的镜像
我们可以使用 docker build --no-cache 跳过缓存
- ADD 和 COPY 将会计算文件的 checksum 是否改变来决定是否利用缓存
- RUN 仅仅查看命令字符串是否命中缓存,如 RUN apt-get -y update 可能会有问题
如一个 node 应用,可以先拷贝 package.json 进行依赖安装,然后再添加整个目录,可以做到充分利用缓存的目的。
使用alpine镜像
使用alpine镜像会比lastest镜像体积小很多,slim则体积更小,可在官网自行选择。
使用文件存储服务
分析一下 50M+ 的镜像体积,nginx:10-alpine 的镜像是16M,剩下的40M是静态资源。
如果把静态资源给上传到文件存储服务,即OSS,并使用 CDN 对 OSS 进行加速,则没有必要打入镜像了,此时镜像大小会控制在 20M 以下
关于静态资源,可以分类成两部分
- /static,此类文件在项目中直接引用根路径,打包时复制进 /public 下,需要被打入镜像
- /build,此类文件需要 require/import 引用,会被 webpack 打包并加 hash 值,并通过 publicPath 修改资源地址。可以把此类文件上传至 oss,并加上永久缓存,不需要打入镜像
FROM node:10-alpine as builder
ENV PROJECT_ENV production
ENV NODE_ENV production
# http-server 不变动也可以利用缓存
WORKDIR /code
ADD package.json /code
RUN npm install --production
ADD . /code
# npm run uploadOss 是把静态资源上传至 oss 上的脚本文件
RUN npm run build && npm run uploadOss
# 选择更小体积的基础镜像
FROM nginx:10-alpine
COPY --from=builder code/public/index.html code/public/favicon.ico /usr/share/nginx/html/
COPY --from=builder code/public/static /usr/share/nginx/html/static
最终优化效果
用了alpine镜像和npm i --production改进Dockerfile后,最终镜像体积减少为之前的1/5左右,优化后Dockerfile
FROM node:17-alpine as builder
WORKDIR /app
COPY package.json .
RUN npm install --registry=http://registry.npm.taobao.org --production
COPY . .
RUN npm run build
FROM nginx:1.21.6-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
优化前体积
优化后体积
删除多余镜像
在更新了几次镜像后,服务器会出现部分多余的镜像
查看所有镜像体积命令
docker images | awk '{print $0}'
删除多余镜像,可以在部署前把之前/后删除多余的镜像
docker system prune
代码规范校验和自动生成CHANGELOG
CICD架子基本搭起来后,还需配置代码规范的流程。在代码提交时难免有人不会遵循规范,commit message 校验和代码规范校验可以通过 husky 约束,在提交时校验通过方能提交。
husky 用来在 git 钩子里运行命令有问题时阻止过不去钩子的。一般用来执行eslint、run test、commitlint 相关命令。这里使用的是7.0版本的husky。
commit message 校验
commintlint 用于校验提交信息,可以配置校验规则。新建一个 .commitlintrc.js 或 commitlint.config.js。commitlint会找到这个文件,按文件中指定的 extends 去校验 commit 信息。也可以自定义设置一些规则。安装 husky & commitlint
npm i -D husky commitlint
目前提交信息的规范有常见的angular版本,也可以自行配置规则。
angular版本
@commitlint/config-conventional是按 angluar 规范校验的,安装依赖
npm i -D @commitlint/config-conventional
.commitlintrc.js 如下
module.exports = {
extends: ['@commitlint/config-conventional']
};
自定义
还可以自定义 commit message 规则,.commitlintrc.js 如下
/*
* rules type enum description
* wip: 功能开发中,需要临时提交代码
* feat: 新功能,已完成该功能的情况用这个,没完成用 wip
* fix: 修复bug
* docs: 撰写文档
* style:代码格式(不影响代码运行的变动)
* refactor:重构(既不是新增功能,也不是修改 bug 的代码变动)
* test:增加测试
* example:示例(仅用于修改 example/*)
* merge: 解决冲突提交
* revert: 回滚代码
* chore: 其他更新
* perf: 提高性能的代码更改
* ci: 对CI配置文件和脚本的更改(示例范围:Travis、Circle、BrowserStack、SauceLabs
* build: 影响构建系统或外部依赖项的更改(示例范围:rollup、dockerfile、npm)
*/
module.exports = {
rules: {
'type-enum': [
2,
'always',
[
'wip',
'feat',
'fix',
'docs',
'style',
'refactor',
'test',
'example',
'merge',
'revert',
'chore',
'perf',
'ci',
'build'
]
],
'type-case': [0],
'type-empty': [0],
'scope-empty': [0],
'scope-case': [0],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [0, 'always', 72]
}
}
最后需要在husky强制校验commit信息,可以通过两种方式初始化husky
npx husky-init
会比 npx husky install
多生成一个 pre-commit
文件,该文件可用于提交前校验eslint和测试用例,pre-commit
也可以后面自己创建。大家可自行选择
npx husky install
npx husky-init
在.husky文件夹内创建commit-msg文件(注意不要建到husky/_那个文件夹里),内容如下:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"
这时我们再提交代码的时候会走到 commit-msg 这个钩子里,执行我们写好的校验 commit 的命令,当commit 信息不符合规范时,commitlint 会提示哪里规则不对,husky 会拦住当前 commit 信息提不上了。就实现了强制遵守定义好的 commit 规范,不能随便提 commit 信息了。
代码规范校验和测试
虽然编辑器都有格式化代码的设置,但大家设置不一致,无法统一代码风格,所以可通过eslint统一风格,还可以通过eslint修复简单的代码规范,并在提交代码前校验代码规范和测试。
首先需要安装 lint-staged,如果不用 lint-staged,直接在 pre-commit 钩子里执行 npm run lint,这样会有个问题,如果项目大了,你只修改一个文件,但它仍然会校验src下所有文件,就会导致提个代码慢的要死等半天。而 lint-staged 就能解决这个问题,它只会校验你修改的那部分文件。
还需安装eslint,prettier可根据实际情况自行选择,因为prettier和eslint的规则有些不一样,可以互补。样式规则可通过stylelint设置,一般项目配置如下:
npm i -D lint-staged eslint prettier
package.json
"scripts": {
"lint": "npx eslint src/**/*.{js,jsx,vue,ts,tsx} --fix",
"test": "your-cmd"
},
"lint-staged": {
"src/**/*.scss": [
"stylelint --fix"
],
"src/**/*.{js,jsx,vue,ts,tsx}": [
"eslint --fix",
"prettier --write"
]
}
在.husky文件夹内pre-commit文件如下:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged
npm run test
vue3+ts配置
如果是vue3项目还需要额外插件识别vue语法和ts语法:
- plugin:vue/vue3-recommended,plugin:vue/vue3-essential:识别vue的变量 如'Page',选其一即可
- @vue/typescript/recommended:识别ts关键字,如interface等,该模块在 @vue/eslint-config-typescript 中
- prettier, @vue/prettier:格式化代码
安装依赖:
npm i -D @vue/eslint-config-typescript @vue/eslint-config-prettier
.eslintrc.js 如下:
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/typescript/recommended',
'prettier',
'@vue/prettier'
],
plugins: ['@typescript-eslint', 'prettier'],
parserOptions: {
ecmaVersion: 2017
},
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error']
},
globals: {
defineProps: 'readonly'
}
}
注意事项
eslint:recommended
会误判一些vue的ts全局变量,如withDefaults
,defineEmits
- eslint 的 rules 配置
no-unused-vars
会误判一些变量,如e
,visible
,所以需要ts变量配置@typescript-eslint/no-unused-vars
vite配置
使用了vite脚手架还需安装插件在浏览器中显示eslint报错:
npm i -D vite-plugin-eslint
vite.config.ts
import eslintPlugin from 'vite-plugin-eslint'
export default defineConfig({
plugins: [eslintPlugin()],
})
遇到的问题
提交代码时遇到报错:error: cannot spawn .husky/pre-commit: No such file or directory
可能是换行符导致的问题,重新初始化husky脚本文件即可
npx husky-init
自动生成 CHANGELOG
CHANGELOG 是记录项目所有的commit信息并归类版本,可以快速跳转到该条commit记录,甚至可以显示修改人信息一眼发现bug的创建者。它能让你方便知道项目里哪个版本做了哪些功能有哪些bug等信息。也方便排查bug,对于提交记录一目了然,不用一个一个去翻去查。一般只在上线合并到master分支才生成 CHANGELOG。
这里直接用 standard-version 来实现自动生成 CHANGELOG 了。 conventional-changelog 就不聊了,毕竟是它推荐用 standard-version (这都是同一个团队的东西,基于conventional-changelog实现的)。关于为什么推荐 standard-version,大家自行阅读官方解释吧~
安装依赖
npm i -D standard-version
package.json
{
"scripts": {
"release": "standard-version"
}
}
standard-version 提供自定义配置不同类型对应显示文案,在根目录新建 versionrc.js 文件,这里示例一个 versionrc.js 内容:
module.exports = {
"types": [
{ "type": "feat", "section": "✨ Features | 新功能" },
{ "type": "fix", "section": "🐛 Bug Fixes | Bug 修复" },
{ "type": "init", "section": "🎉 Init | 初始化" },
{ "type": "docs", "section": "✏️ Documentation | 文档" },
{ "type": "style", "section": "💄 Styles | 风格" },
{ "type": "refactor", "section": "♻️ Code Refactoring | 代码重构" },
{ "type": "perf", "section": "⚡ Performance Improvements | 性能优化" },
{ "type": "test", "section": "✅ Tests | 测试" },
{ "type": "revert", "section": "⏪ Revert | 回退" },
{ "type": "build", "section": "📦 Build System | 打包构建" },
{ "type": "chore", "section": "🚀 Chore | 构建/工程依赖/工具" },
{ "type": "ci", "section": "👷 Continuous Integration | CI 配置" }
]
}
执行 npm run release
,就会根据commit信息自动生成 CHANGELOG.md 文件。当commit type是 feat和fix的时候执行这个命令,它会自增版本号。
代码校验和CHANGELOG的CI自动化
做完一系列的校验和CHANGELOG的配置后,还需在CI脚本上配置才能实现自动化工程,完整的gitlab-ci.yml文件如下:
image: node:alpine
# 设置缓存
cache:
paths:
- node_modules/
stages:
- eslint
- build
- changelog
- deploy
eslint:
stage: eslint
tags:
- vite
script:
- npm install
- npm run lint
- echo "eslint pass!"
build:
stage: build
tags:
- vite
script:
- npm run build
- echo "build success!"
changelog:
stage: changelog
tags:
- vite
script:
- npm run release
only:
- master
deploy:
stage: deploy
image: docker
tags:
- vite
script:
- docker build -t folive .
- if [ $(docker ps -aq --filter name=mylive-container) ]; then docker rm -f mylive-container;fi
- docker run -d -p 8001:80 --name mylive-container folive
- echo 'deploy docker image success. visit http://120.79.76.143:8001'
最后附上完整代码
参考文章