最近刚好需要将一个一年前的 Spring Boot 项目跑起来,但是懒得在服务器上配置 Java 相关的依赖,于是我们首先想到容器。
目标
- 安装 Docker
- 打包 Docker 镜像
- 从零开始
- 优化镜像的性能
- build-image goal
- 借力 GitHub Actions 自动化
- 将容器镜像推送到远端仓库
- GitHub Container Registry
- Amazon Elastic Container Registry
- 使用 docker-compose 跑起来
- 连接 MySQL
- 持久化 Volume
- 端口暴露
- 自动重启
安装 Docker
在 Linux 下按照官方教程安装 Docker 十分简单,加源加key再装包即可。这里以 Deepin Linux 20.10 (基于 Debian Buster),使用 Matrix 镜像源为例,以 root 身份执行:
这样便结束了。可以通过curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -echo 'deb [arch=amd64] https://mirrors.matrix.moe/docker-ce/linux/debian buster stable' > /etc/apt/sources.list.d/docker.listapt update && apt install docker-ce docker-ce-cli containerd.io
sudo docker info验证一下。 ```bash Client: Context: default Debug Mode: false Plugins: app: Docker App (Docker Inc., v0.9.1-beta3) buildx: Build with BuildKit (Docker Inc., v0.5.0-docker)
Server: Containers: 2 Running: 0 Paused: 0 Stopped: 2 Images: 1 Server Version: 20.10.1 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: false Logging Driver: json-file Cgroup Driver: cgroupfs Cgroup Version: 1 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: inactive Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc Default Runtime: runc Init Binary: docker-init containerd version: 269548fa27e0089a8b8278fc4fc781d7f65a939b runc version: ff819c7e9184c13b7c2607fe6c30ae19403a7aff init version: de40ad0 Security Options: apparmor seccomp Profile: default Kernel Version: 5.8.14-amd64-desktop Operating System: Deepin 20.1 OSType: linux Architecture: x86_64 CPUs: 12 Total Memory: 15.52GiB Name: rwong-OMEN ID: GZQO:FSY4:ATQV:6X3L:WMBV:FD64:LSKT:3664:MAO5:Y2L4:4AH5:CN7I Docker Root Dir: /var/lib/docker Debug Mode: false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false
<a name="0pqaG"></a>## 为一个 Spring Boot 项目打包 Docker 镜像Reference:[https://spring.io/guides/gs/spring-boot-docker/](https://spring.io/guides/gs/spring-boot-docker/)<a name="VcWVX"></a>### 从零开始为了打包容器镜像,我们需要撰写一个 Dockerfile,大概类似于```dockerfileFROM openjdk:8-jdk-alpineARG JAR_FILE=target/*.jarCOPY ${JAR_FILE} app.jarENTRYPOINT ["java","-jar","/app.jar"]
这意味着我们需要先在外边将程序编译好,起码得有个 app.jar 文件。有了的话我们直接放进去就可以变成一个可以运行的容器镜像了。
优化一下
Spring Boot 是可以将应用本体与依赖分离的,如果我们把它们放在不同的层可以提高性能(?)。所以我们的 Dockerfile 可以变成这样:
FROM openjdk:8-jdk-alpineRUN addgroup -S spring && adduser -S spring -G springUSER spring:springARG DEPENDENCY=target/dependencyCOPY ${DEPENDENCY}/BOOT-INF/lib /app/libCOPY ${DEPENDENCY}/META-INF /app/META-INFCOPY ${DEPENDENCY}/BOOT-INF/classes /appENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
build-image goal
Spring Boot 提供了一个 build plugin 使得你在不编写 Dockerfile 的情况下也可以直接打包容器镜像。于是我们连上边的 Dockerfile 都不用理了,直接:
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=m208/sysu-pan-server
最后生成的容器镜像名字是 docker.io/m208/sysu-pan-server:latest 。
借力 GitHub Actions 自动化
如果当我们更改完代码并提交到 Git 仓库时,就有人帮我们打包好 Docker 镜像那自然是最好不过了。这里我们借助 GitHub Actions 可以轻松实现。
新建一个 .github/workflows/whatever-name-you-like.yml :
# This workflow will build a Java project with Maven# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-mavenname: Java CI with Mavenon:push:branches: [ master ]pull_request:branches: [ master ]jobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- name: Set up JDK 11uses: actions/setup-java@v1with:java-version: 11- name: Build ocker image with Mavenrun: |chmod +x mvnw./mvnw spring-boot:build-image -Dmaven.test.skip=true -Dspring-boot.build-image.imageName=m208/sysu-pan-server
搞定。当每次 master 分支有新的 push 或 pull request 时,GitHub 都会自动跑 CI 帮你打包镜像。
推送到远端仓库
那么问题来了,在 GitHub Actions 上自动打包的容器镜像,怎么用呢?很可惜,用不了。因为我们 build 完之后,负责 CI 的机器就跑路了,所以我们必须在它跑路之前把它推送到一个可以持久存储镜像的地方。
世界上最大的镜像仓库非 Docker Hub 莫属,然而:
- Docker Hub 在国内很慢,很慢,很慢。(虽然有一些不太好用的镜像站可以解决这个问题)
- Docker Hub 开始限制 Pull 请求的速率。
环顾四周,可以用来免费存放公开镜像的还有 GitHub Container Registry (ghcr.io) 和 Amazon Elastic Container Registry (ecr.aws)。
GitHub Container Registry
ghcr 用起来十分简单,和 Docker Hub 是类似的,区别在于 Docker ID 变成了你的 GitHub username,而且你需要开启 Feature preview 里的一个选项(因为 ghcr 还在 beta 阶段)。对于公开的镜像而言,ghcr 是完全免费的,也没有 Docker Hub 那种少得可怜的 Pull rate limit。
使用时,先创建一个 write:packages scope 的 PAT,然后存为目标项目的一个 secret,比如 CT_TOKEN ,然后在 GitHub Actions 里加多几行:
- name: Build and push Docker image with Mavenrun: |chmod +x mvnw./mvnw spring-boot:build-image -Dmaven.test.skip=true -Dspring-boot.build-image.imageName=m208/sysu-pan-serverecho ${{ secrets.CR_TOKEN }} | docker login ghcr.io -u robinWongM --password-stdindocker tag docker.io/m208/sysu-pan-server:latest ghcr.io/robinwongm/sysu-pan-server:latestdocker push ghcr.io/robinwongm/sysu-pan-server:latest
首先登录到 ghcr,然后将现有的镜像打 tag 为 ghcr.io 前缀,之后 docker push 就搞定了。可以在自己的 Packages 列表里看到这个镜像(不要忘记设置为 Public)。
那么,在国内用起来怎么样呢?很可惜,刚刚制作完成的镜像我拉了半个小时还没拉完。
Amazon Elastic Container Registry
既然 ghcr 的速度这么拉胯,我们就再试试别的方案。
按照云服务商的尿性,ECR 和平常的 Docker Hub 用起来则略微有所区别。首先你需要先开通这个服务,然后你需要事先创建一个 repository,创建时只能指定最后的名字,命名空间是系统随机生成的一个无冲突字符串。
之后我们就可以往这个 repository 里推送镜像了。AWS 传统艺能 IAM 用户此时登场,我们需要为 GitHub Actions 创建一个 IAM 用户,分配其所属的组,并为这个组设定其拥有的权限。总的来说,非常复杂,非常安全。
配完后可以拿到该 IAM 用户的 Access ID 和 Access Key,将其存到 GitHub 仓库的 Secrets 里,我们姑且就叫它们 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 好了。接着往 GitHub Actions Workflow 里加一点东西:
- name: Configure AWS credentialsuses: aws-actions/configure-aws-credentials@v1with:aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}aws-region: us-east-1- name: Build and push Docker image with Mavenrun: |echo ${{ secrets.CR_TOKEN }} | docker login ghcr.io -u robinWongM --password-stdinaws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/s5q8x3r4chmod +x mvnw./mvnw spring-boot:build-image -Dmaven.test.skip=true -Dspring-boot.build-image.imageName=m208/sysu-pan-serverdocker tag docker.io/m208/sysu-pan-server:latest public.ecr.aws/s5q8x3r4/sysu-pan-server:latestdocker push public.ecr.aws/s5q8x3r4/sysu-pan-server
如此我们便完成了将镜像推送到 AWS ECR 的任务。速度如何呢?
140+M 的镜像在教育网 IPv6 下满速(2MB/s)下载,1 分多钟便搞定。
AWS ECR 的公开仓库和私有仓库一样也是要收钱的,只不过有免费额度: 存储配额:50 GB 匿名拉取(按来源 IP 算):每个月 500 GB,超过后不能再拉取 使用 AWS 账号登录后拉取:每个月 5 TB,超出后计费
总的来说,看起来还阔以。
跑起来跑起来
我们的示例镜像在实际运行中需要
- 连接 MySQL
- 读取外部提供的配置
- 读写本地数据目录并且需要持久化
- 暴露端口
- 运行时出错自己退出了需要重启(守护)
让我们来写一个简单的 docker-compose.yml 解决这几个问题。
ersion: "3.8"networks:sysu-cloud:driver: bridgeservices:sysu-cloud-backend:image: public.ecr.aws/s5q8x3r4/sysu-pan-server:latestports:- 8080:8080volumes:- /data/sysu-cloud:/workspace/data- ./application.properties:/workspace/BOOT-INF/classes/application.properties:ronetworks:- sysu-clouddepends_on:- mysqlrestart: unless-stoppedmysql:image: public.ecr.aws/bitnami/mysql:5.7.32-debian-10-r71environment:- ALLOW_EMPTY_PASSWORD=yesvolumes:- ./mysql-persistence:/bitnami/mysql/data- ./init-db.sql:/docker-entrypoint-initdb.d/sysu_cloud.sqlnetworks:- sysu-cloudrestart: unless-stopped
声明化的方式谁不爱呢?写完我们想要的 ports, volumes, networks 和 dependencies,之后 docker-compose up -d 即可把复杂的事情全部搞定。
通过 docker-compose logs 即可看到所有容器的日志。
此时我们通过机器上的 8080 端口即可访问到所要部署应用的 8080 端口。
