⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7439403354056359947 「别惹CC」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

引子

在许多小团队中,开发人员不仅要编写应用代码,还需要负责将应用部署到生产环境。对于没有专职运维人员的团队,每次手动部署都不仅费时费力,还容易出错。因此,采用CI/CD(持续集成/持续部署) 来实现自动化部署,成为了解决这一问题的最佳方式。

本文将演示如何使用GitHub Actions和阿里云 Kubernetes(ACK) 来实现Java Web 应用的自动化部署。我们的目标是,开发人员无需将精力耗费在运维任务上,而是通过现成的云服务来简化部署过程。

需要说明的是,本文的核心目标是降低部署的运维成本,因此选择了多个云服务来演示整个流程,但在实际应用中,大家可以根据团队的情况进行调整。

例如,如果团队有足够的精力,也可以自行部署CI/CD工具,如Jenkins、GitLab CI,而Kubernetes 提供商的选择也非常多,甚至可以自行搭建Kubernetes集群。

本文的重点不在于具体工具的选择,而在于对整个自动化部署流程的掌握。一旦理解了基本流程,切换到其他工具或平台时,上手速度会非常快,因为它们的流程都大差不差的。

认识Docker与Kubernetes

在正式开始操作前,如果是第一次接触的同学,可以先阅读下第二章节,来对Docker与Kubernetes建立起一个认识:

Docker

如果把一个应用程序比作一件商品,那么Docker就像是为这件商品打包的“集装箱”。在没有Docker的年代,开发人员在本地运行的应用,往往在部署到服务器时会遇到各种问题,比如依赖库不一致、操作系统版本不同等。就像你将一件精心制作的商品从一个国家运到另一个国家,可能会因为不同的运输环境、气候条件等因素,导致商品在到达目的地时无法正常使用。

Docker就像是一个标准化的“集装箱”,它能把你的应用程序和它需要的所有依赖打包在一起,形成一个独立的“镜像”image。无论你是在开发环境、测试环境,还是生产环境,Docker镜像都能确保应用在每个环境中以相同的方式运行,就像集装箱能在全球范围内标准化运输一样。它解决了“我本地运行没问题,为什么服务器上就不行?” 这个历史难题。

用个例子说明一下:你要制作一份美味的蛋糕,并寄给朋友。没有Docker的话,你只能把蛋糕装进一个普通的纸箱,然后寄出去。结果朋友收到后发现,蛋糕因为运输颠簸、天气变化,变得面目全非。而使用Docker,就相当于你把蛋糕放进一个坚固的保温集装箱,确保在任何运输条件下,蛋糕能保持新鲜、完整。无论你把这个蛋糕寄到哪里,朋友收到的蛋糕都和你制作时一模一样。

Kubernetes

上文中提到Docker是打包应用的“集装箱”,那么Kubernetes就是管理这些集装箱的“码头系统”。在实际生产环境中,应用程序往往需要部署到多个服务器上(或者称为节点),还需要根据流量的变化,灵活调整应用实例的数量。手动管理这些工作量大且容易出错,就像手工管理码头上成百上千个集装箱一样,效率低下。

Kubernetes 的出现,让这一切变得自动化和可控。它提供了一个强大的管理平台,用来自动调度和管理运行中的 Docker容器。Kubernetes不仅能够帮你决定应用(容器)应该在哪台服务器上运行,还能根据流量自动扩容或者缩容,确保应用始终保持高可用性。

再举个例子:假设你经营一个全球连锁的快递公司,每天都有成千上万的集装箱到达各个港口。如果没有一个智能的管理系统,你需要手动决定每个集装箱应该放在哪个仓库、什么时候发货、运输中如何调度,甚至在货物需求增加时,你还得临时增加人手来处理这些集装箱。

而Kubernetes就是这个智能的“码头管理系统”。它会根据你的需求,自动分配集装箱到各个港口,确保货物按时到达。如果某个港口的集装箱积压了,它可以自动将多余的货物调度到别的港口,保证高效运转。当货物需求增加时,它甚至可以“雇佣”更多的卡车来处理这些集装箱——这就是Kubernetes的自动扩展功能。

整体的架构与关系可以用这张图表示:

  • Docker: 将应用打包成标准化的容器,确保在不同环境下都能一致运行。
  • Kubernetes: 管理和调度这些容器,自动分配资源、扩展应用,并保证高可用性。

实操

在上文中已经明确了我们本次所使用的服务,这里再详细说明下每个服务所起的作用:

  • GitHub Actions: GitHub 的 CI/CD 工具,帮助我们实现代码提交后的自动化流程。
  • 阿里云 Kubernetes(ACK): 阿里云的托管 Kubernetes 服务。
  • 阿里云容器镜像服务(ACR): Docker 镜像仓库,用于存储和管理 Docker 镜像。
  • Spring Boot: 作为示例的 Java Web 应用框架。

同时,再对我们整个操作流程画图说明一下,如下:

1.开发Web应用

本篇的重点不在于应用,所以我们用Spring Boot简单起个应用,写个hello World的接口可以访问到就行,主要代码如下:

@RestController
public class HelloController {

@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}

编写完成后,先在本地启动测试下,确保应用可以正常使用,如下:

然后把它推送到Github仓库即可。

2.创建Kubernetes集群

直接登录阿里云控制台,搜索ACK即可找到容器服务 Kubernetes,如下:

我们这里只是演示,越简单越好,就买ACK托管版就可以了,配置就选最便宜的,然后一路下一步就行,如下:

等待几分钟,集群创建成功,如下:

集群创建完成后,进入集群的详情页面,点击连接信息,下载kubeconfig文件,这个文件包含你的集群配置信息,后续会用到它来访问集群。这里提一下,我们要的是公网访问的配置文件,如下:

3.创建阿里云容器镜像服务(ACR)

我们需要一个存储Docker镜像的地方,阿里云容器镜像服务(ACR)就是用来存储和管理 Docker 镜像的。在控制台搜索容器镜像服务,如下:

这里创建个人实例即可,如下:

点击 创建命名空间,为镜像仓库创建一个命名空间,如下:

创建完命名空间后,再点击 创建镜像仓库,如下:

创建完成后,可以得到仓库的连接相关信息,如下:

这些凭证将用于GitHub Actions登录ACR,推送Docker 镜像。

4.配置GitHub仓库Secrets

接下来,我们需要将阿里云的相关凭证添加到GitHub仓库的Secrets中,这样GitHub Actions才能安全地访问阿里云的资源。

首先打开你的 GitHub 仓库,点击 Settings ,然后选择左侧的 Secrets and variables下的Actions,再点击New repository secret,如下:

然后添加相关的Secrets,这里需要注意一下,因为后续的配置需要用到它们,所以这里的名称需要和后续的配置文件中保持一致,否则读取不到打包会报错的,如下:

  • ALIYUN_REGISTRY_USERNAME:阿里云容器镜像服务(ACR)的用户名。
  • ALIYUN_REGISTRY_PASSWORD:阿里云容器镜像服务(ACR)的密码。
  • ALIYUN_REGISTRY_URL:ACR 镜像仓库的登录地址。
  • ALIYUN_NAMESPACE:ACR 命名空间。
  • ALIYUN_REPO_NAME:ACR 镜像仓库名称。
  • ALIYUN_KUBECONFIG:将之前下载的 kubeconfig 文件内容复制粘贴到这里,用于后续 GitHub Actions 通过 kubectl 访问 Kubernetes 集群。

5.创建GitHub Actions工作流

接下来在项目的根目录中,创建一个文件 .github/workflows/ci-cd.yml(Ps:文件名随意,目录必须一致),并添加以下内容:

name: CI/CD for Spring Boot Hello World

on:
push:
branches:
- master

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '17'

- name: Build with Maven
run: mvn clean package

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to ACR
run: |
docker login \
--username=${{ secrets.ALIYUN_REGISTRY_USERNAME }} \
${{ secrets.ALIYUN_REGISTRY_URL }} \
--password=${{ secrets.ALIYUN_REGISTRY_PASSWORD }}

- name: Build and Push Docker image
run: |
# 构建本地镜像
docker build -t springboot-hello-world:latest .

# 打标签
docker tag springboot-hello-world:latest ${{ secrets.ALIYUN_REGISTRY_URL }}/${{ secrets.ALIYUN_NAMESPACE }}/${{ secrets.ALIYUN_REPO_NAME }}:latest

# 推送镜像
docker push ${{ secrets.ALIYUN_REGISTRY_URL }}/${{ secrets.ALIYUN_NAMESPACE }}/${{ secrets.ALIYUN_REPO_NAME }}:latest

deploy:
needs: build
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'

- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.ALIYUN_KUBECONFIG }}" > $HOME/.kube/config
chmod 600 $HOME/.kube/config

- name: Deploy to Kubernetes
run: |
kubectl set image deployment/springboot-hello-world springboot-hello-world=${{ secrets.ALIYUN_REGISTRY_URL }}/${{ secrets.ALIYUN_NAMESPACE }}/${{ secrets.ALIYUN_REPO_NAME }}:latest

项目打包需要我们编写Dockerfile,也很简单,根据实际情况简单配下就行,示例如下:

FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

现在我们往Master分支上提交代码就会自动触发action来帮助我们打包镜像,如下:

在实际业务场景中,我们会存在测试、生产等多个环境的分支,都需要进行自动化构建,也是很简单,在上面的配置里加上其他分支即可。由于我们的应用非常简单,所以整个打包的时间也相对较短,打包成功后,如下:

6.创建Kubernetes集群配置:

最后还需要我们在项目里添加一下Kubernetes集群的相关配置,如下:

apiVersion: apps/v1
kind: Deployment
metadata:
name: springboot-hello-world
spec:
replicas: 1
selector:
matchLabels:
app: springboot-hello-world
template:
metadata:
labels:
app: springboot-hello-world
spec:
containers:
- name: springboot-hello-world
image: registry.cn-hangzhou.aliyuncs.com/my-app/springboot-hello-world:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: springboot-hello-world-service
spec:
selector:
app: springboot-hello-world
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer

提交到仓库后,可以在Kubernetes集群的控制台里看到我们的服务实例了,如下:

7.访问以及版本管理

部署成功后,访问下公网ip,也可以正常访问我们的应用,如下:

按照我们现有的配置,我们后续每次向Master分支提交代码都会自动打包以及更新我们的服务,比如我们在接口的响应上加上123,代码如下:

@RestController
public class HelloController {

@GetMapping("/hello")
public String hello() {
return "Hello World! 123";
}
}

等待打包成功后,再次访问,会看到已经更新了,如下:

如果我们发布的新版本应用有BUG,需要回滚,操作也是非常简单,进入到集群详情-历史版本里就可以看到我们的版本记录,如下:

想要回滚到哪个版本直接点击回滚到该版本即可。

小结

相信大家看到这里对于整个流程也有了清晰的认识,可以根据自己的实际选择适合的来替换某个节点的服务或工具。另外,可能有人会说把代码发到Github上等于把业务泄露了。

我这里说下我的看法:本身针对小团队肯定越快越好,自然不用搭建的是最快的。另外,把仓库设为私有仓库,开发人员的权限简单配置下就足够用了。等你体量做大了,自然也不需要用它了。

文章目录
  1. 1. 引子
  2. 2. 认识Docker与Kubernetes
    1. 2.1. Docker
    2. 2.2. Kubernetes
  3. 3. 实操
    1. 3.1. 1.开发Web应用
    2. 3.2. 2.创建Kubernetes集群
    3. 3.3. 3.创建阿里云容器镜像服务(ACR)
    4. 3.4. 4.配置GitHub仓库Secrets
    5. 3.5. 5.创建GitHub Actions工作流
    6. 3.6. 6.创建Kubernetes集群配置:
    7. 3.7. 7.访问以及版本管理
  4. 4. 小结