🐳 📖

基于 Github Actions 和 Docker Hub 的项目开发部署流程

📆 2020-12-15

🏷 DockerGithub ActionsCICD

🖍 本文主要从开发者的视角,介绍一种基于 Docker 容器和 Github Actions 的开发,测试和部署流程。

🏂 正文 👇

本文主要从开发者的视角,介绍一种基于 Docker 容器和 Github Actions 的开发,测试和部署流程。

大致来说,主要有以下关键步骤:

  • 使用 Docker 将项目容器化;
  • 使用 Github Actions 监听 Github 仓库特定事件的触发(这里是 push 事件),然后执行相应的自动化操作;
  • 这里的自动化操作包括:运行项目的测试用例;如果测试用例全部通过,则构建生产版本镜像到 Docker Hub 对应的项目仓库;
  • Docker Hub 仓库设置 Webhook 钩子,订阅新镜像推送事件,来触发自动部署;

项目代码可以在 github找到。

1. 项目容器化

Docker 是个好东西。它为开发者提供了一个使用容器构建,运行和分享应用程序的平台。它降低了 Linux 容器的使用门槛,使普通开发者也可以轻易的实现应用程序(及其环境)的相互隔离。
这让你的开发环境不会再轻而易举就变得凌乱。而且你也可以轻松的构建打包自己的应用程序及其环境。 然后方便的将其部署到其他任何安装了 Docker 的开发环境中。

可以很方便的将项目 Docker 化。首先需要在开发环境安装 Docker。然后,在项目根目录添加一个名为 Dockerfile 的文本文件。
比如下面 👇 这样:

1FROM node:14.15.1 as builder
2
3WORKDIR /usr/app
4
5COPY package.json /usr/app/
6
7RUN npm install --silent --no-cache --registry=https://registry.npm.taobao.org
8
9COPY ./ ./
10
11RUN npm run build && mv /usr/app/build/* /usr/src && rm -rf /usr/app
12
13FROM nginx:latest
14
15COPY --from=builder /usr/src /usr/share/nginx/html
16
17EXPOSE 80
18
19CMD ["nginx", "-g", "daemon off;"]

关于 Dockerfile 文件的语法详情请参考Docker 官网。 有了 Dockerfile 文件之后,我们通过在命令行键入 docker build -t <镜像命> . 命令来生成一个 Docker 镜像。 Docker 镜像提供了运行你打包的程序的任何东西,比如:代码或者二进制码,运行时,项目依赖和你应用程序需要的文件系统。 然后,你可以通过docker run <容器名称> <镜像名称>来生成并运行一个 Docker 镜像的 Docker 容器。 Docker 容器可以认为是 Docker 镜像 的 "实例"。 你可以通过 Docker 镜像 “实例化” 多个 Docker 容器,然后愉快的将其跑起来。 但是涉及到实际项目的时候,可能我们一个项目需要同时跑多个 Docker 容器。
这个时候仅仅通过定义 Dockerfile 的这种方法就显得有些吃力。所以 Docker 又提供了 docker-compose 来应对这种多 Docker 容器相关联的场景。
你可以在项目的根目录下定义一个 docker-compose.yml 文件。 比如下面 👇 这样的:

1version: '3.9'
2
3services:
4 web_prod:
5 build: .
6 ports:
7 - '80:80'
8 container_name: 'web_prod'

docker-compose.yml 中配置你项目用到的容器,然后通过一键执行 docker-compose up 来生成项目中定义的多个镜像并使之运行。 关于 docker-compose.yml 文件的语法详情请参考Docker 官网 现在你的应用程序就成功跑在与系统环境相对隔离的 Docker 容器里了。其实运行在容器中是一把双刃剑,一方面容器的隔离效果为应用自身和开发环境带来了更安全更解耦的好处, 另一方面,隔离效果也带来了容器之间,和容器与环境之间相互访问的不便之处。当然这种不便主要体现在你刚上手 Docker 的时候,可能会掉入其中的坑。
你需要时刻牢记不同的 Docker 镜像运行在不同的环境中。所以当需要容器之间相互通信的时候,比如一个典型 node 后台项目, 你可能需要用到 node, 数据库,nginx 等多个应用程序, docker 鼓励一个应用一个容器的做法,比如其官方镜像库中的官方镜像就是这样的,你可以在其中找到node的镜像,mysql的镜像,nginx的镜像。然后就像我们之前提到的,你可能需要在项目根目录的 docker-compose.yml 文件中定义这些镜像的使用,就像下面 👇 这样:

1version: '3'
2services:
3 mysql:
4 image: 'mysql:8.0.22'
5 # 同一docker network 下的容器可以通过 容器名 相互引用
6 container_name: 'mysql'
7 networks:
8 - ivi_web_net
9 environment:
10 MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
11 MYSQL_DATABASE: 'admin-server'
12 restart: unless-stopped
13 volumes:
14 - server_data_mysql:/var/lib/mysql
15 server:
16 build: ./server
17 container_name: 'server'
18 depends_on:
19 - mysql
20 networks:
21 - ivi_web_net
22 restart: unless-stopped
23 nginx:
24 build: ./nginx
25 container_name: 'nginx'
26 ports:
27 - '8080:8080'
28 networks:
29 ivi_web_net:
30 depends_on:
31 - server
32 volumes:
33 - ./ivi_admin/dist:/var/www/html/admin
34 - ./ivi_offical/dist:/var/www/html/offical
35
36networks:
37 ivi_web_net:
38volumes:
39 server_data_mysql:

这里为项目定义了三个镜像,里面包括了服务端,数组库,还有 nginx。

现在如果你需要在上面定义的名为 server 的 node 容器中连接数据库,那么就可以使用 mysql 数据库容器的 container_name: mysql 来引用到数据库。

还有一个场景是在容器中想访问宿主机。在 linux 系统中,可以通过 docker0 来指代宿主机的 IP 地址,在 PC 或者 Mac 中,则需要通过 host.docker.internal这个特殊的 DNS 名称来访问宿主机 IP 地址。 具体请参考 stackoverflow 中的这个回答.

在实际项目中,我们可能需要定义不同的开发环境,比如 development, test, production 等。虽然 Dockerfile 可以多阶段构建。但是我还是觉得 Dockerfile 配置针对不同的环境写在不同的配置文件里比较清晰。所以,我们可能会有一个针对 development 的 Dockerfile.dev 配置文件,比如下面 👇 这样:

1FROM node:14.15.1
2
3WORKDIR /usr/app
4
5COPY package.json /usr/app/
6
7RUN npm install --silent --no-cache --registry=https://registry.npm.taobao.org
8
9COPY ./ ./
10
11CMD ["npm", "start"]
12
13EXPOSE 3000

然后我们在 docker-compose.dev.yml 中配置如下:

1version: '3.9'
2
3services:
4 web_dev:
5 container_name: 'web_dev'
6 build:
7 context: .
8 dockerfile: Dockerfile.dev
9 ports:
10 - '3000:3000'
11 volumes:
12 - .:/usr/app

然后我们在 package.jsonscripts 里加上:"dev:docker": "docker-compose -f docker-compose.dev.yml up" , "build:docker": "docker-compose up" ,分别用来启动 development 环境容器 和 prodduct 环境容器。

到这里项目容器化就完成了。

2. 配置 Github Actions

Github Actions 是一套基于事件的 CI/CD 自动化工具。 Github Actions 可以在 Github 仓库里的 Actions 中配置,比如下面 👇 这样:

1# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
4name: Test then Upload Image CI
5
6on:
7 push:
8 branches: [main]
9
10jobs:
11 test:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v2
15 - name: Cache node modules
16 uses: actions/cache@v2
17 env:
18 cache-name: cache-node-modules
19 with:
20 # npm cache files are stored in `~/.npm` on Linux/macOS
21 path: ~/.npm
22 key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
23 restore-keys: |
24 ${{ runner.os }}-build-${{ env.cache-name }}-
25 ${{ runner.os }}-build-
26 ${{ runner.os }}-
27 - name: Install Dependencies
28 run: npm install
29 - name: Test
30 run: npm test
31 upload_image:
32 runs-on: ubuntu-latest
33 needs: test
34 steps:
35 - name: Set up QEMU
36 uses: docker/setup-qemu-action@v1
37 - name: Set up Docker Buildx
38 uses: docker/setup-buildx-action@v1
39 - name: Login to DockerHub
40 uses: docker/login-action@v1
41 with:
42 username: ${{ secrets.DOCKER_HUB_USERNAME }}
43 password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
44 - name: Build and push
45 id: docker_build
46 uses: docker/build-push-action@v2
47 with:
48 push: true
49 tags: andyhuo/docker_with_github_actions_demo:latest
50 - name: Image digest
51 run: echo ${{ steps.docker_build.outputs.digest }}

上面的 Action 定义了在 push 代码的时候,运行两个任务,第一个任务是运行项目测试,如果测试通过那么构建 Docker 镜像并将构建好的镜像上传到 Docker Hub 对应的仓库。 需要注意的是,上面配置项中的 secrets.DOCKER_HUB_USERNAME 是在 Github 仓库 设置中 Secrets 设置项里设置的字段,对应 Docker Hub 账户的用户名,secrets.DOCKER_HUB_ACCESS_TOKEN 则是在 Docker Hub 账号设置中 Security 设置项里创建的自定义 Token。这里更详细的教程请参考 Docker 官网

3. 通过 Docker Hub 的 webhook 触发生产部署

上面的 Github Actions 自动化流程如果运行成功,那么你配置的 Docker Hub 仓库中就会有相应的镜像了。你可以在任何安装了 Docker 的开发环境中,通过 docker run -d -p 80:80 <project_name> <docker_image> 来下载镜像,并运行相应的容器。或者也可以通过在 Docker Hub 配置相应的 webhook, 在有新镜像推送到仓库的时候触发自动部署。详细信息请参考 Docker 官网

参考资料:

👐 The End 🎉

上一篇 在 Docker 项目中使用 Mysql 数据库🏠下一篇培养优秀品格的15条行为规范 

👇 💬