使用Spring Boot后端和Angular TodoMVC部署到Kubernetes的完整指南

本文详细介绍了如何使用Spring Boot构建后端REST API,集成Angular TodoMVC前端,并通过Docker容器化部署到Kubernetes和OpenShift平台的全过程。

Angular TodoMVC with Spring boot backend, deploy to Kubernetes

应用概述

本文指导您构建一个Spring Boot演示应用程序,并使用JBoss Web Server Operator将其部署在Kubernetes上。该应用程序使用TodoMVC Angular前端,与Spring Boot后端集成。

Todo实体使用JPA注解进行数据库映射。TodoController处理CRUD操作,TodoRepository扩展JpaRepository进行数据库交互。应用程序可以在本地运行,也可以使用Tomcat作为嵌入式服务器进行打包。

部署过程包括构建Docker镜像、推送到存储库以及在OpenShift上部署。提供了H2和PostgreSQL数据库的配置详细信息。

前端:TodoMVC Angular应用

TodoMVC是一个项目,提供使用各种流行JavaScript框架实现的相同"Todo"应用程序。TodoMVC的目标是通过展示不同框架如何使用相同功能解决相同问题,帮助开发人员比较和对比不同的框架和库。

在这个演示应用程序中,我们使用TodoMVC前端Angular示例。项目构建后复制到Java项目的resources目录中,以保持在同一应用程序和根上下文中。

后端:Spring Boot框架

后端使用Spring Boot实现,这是一个旨在简化独立、生产级基于Spring的应用程序开发的框架。它通过提供预配置模板和约定优于配置的方法构建在Spring框架之上,使得以最少的配置设置和部署应用程序变得更加容易。

该项目使用Spring Boot框架和Spring Boot Tomcat Starter。这是一个依赖项,您可以将其包含在Spring Boot项目中,以使用嵌入式Apache Tomcat服务器运行应用程序。这允许您将应用程序打包为包含Tomcat的自包含可执行JAR文件,使得无需配置外部Web服务器即可轻松部署和运行应用程序。

完整源代码可在此处获取。

Todo实体

首先我们创建一个名为Todo的实体。这是我们希望将数据存储到后端数据库的方式。

Todo表示数据库中"待办事项"项目的实体。该类使用Jakarta Persistence(前身为Java Persistence API或JPA)注解将类映射到数据库表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
@Table(name = "todos")
public class Todo {

    // 指定主键由数据库自动生成(自动递增)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 确保标题字段不为空(不能为null或空)且唯一
    @NotBlank
    @Column(unique = true)
    private String title;

    private boolean completed;

    // 将字段映射到数据库中名为"ordering"的列
    @Column(name = "ordering")
    private int order;

    @Column(name = "url")
    private String url;

    // Getter和Setter方法
    ....
}

Todo控制器

TodoController处理与"Todo"项目相关的HTTP请求。它利用多个Spring组件和注解来定义CRUD(创建、读取、更新、删除)操作的端点。

控制器定义以下结构:

  • @RestController:将此类标记为Spring MVC控制器,其中每个方法返回域对象而不是视图。它结合了@Controller@ResponseBody
  • @CrossOrigin:为整个控制器启用跨源资源共享(CORS),允许来自不同源的请求
  • @RequestMapping("/api/todos"):将到/api/todos的HTTP请求映射到此控制器
1
2
3
4
5
6
7
@RestController
@CrossOrigin
@RequestMapping("/api/todos")
public class TodoController {

    @Autowired
    private TodoService todoService;

获取所有Todo

将HTTP GET请求映射到此方法。通过调用todoService.getAllTodos()返回所有待办事项的列表。

1
2
3
4
@GetMapping
public List<Todo> getAllTodos() {
    return todoService.getAllTodos();
}

按ID获取Todo

@GetMapping("/{id}"):将带有路径变量id的HTTP GET请求映射到此方法。它通过ID检索待办事项。

@PathVariable Long id:将id路径变量绑定到方法参数。

ResponseEntity:使用适当的HTTP状态码包装响应。如果找到待办事项则返回200 OK,否则返回404 Not Found。

1
2
3
4
5
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
    Optional<Todo> todo = todoService.getTodoById(id);
    return todo.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}

创建Todo

要创建Todo,我们期望从前端收到带有JSON的请求。

@Transactional:确保方法在事务内执行。

@PostMapping:将HTTP POST请求映射到此方法。它创建一个新的待办事项。

@RequestBody Todo todo:将请求体绑定到方法参数todo。

1
2
3
4
5
@Transactional
@PostMapping
public Todo createTodo(@RequestBody Todo todo) {
    return todoService.createOrUpdateTodo(todo);
}

更新Todo

@PutMapping("/{id}"):将带有路径变量id的HTTP PUT请求映射到此方法。它更新现有的待办事项。

@RequestBody Todo todoDetails:将请求体绑定到方法参数todoDetails。

ResponseEntity:如果更新成功则返回200 OK,否则返回404 Not Found。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Transactional
@PutMapping("/{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todoDetails) {
    Optional<Todo> todo = todoService.getTodoById(id);
    if (todo.isPresent()) {
        Todo todoToUpdate = todo.get();
        todoToUpdate.setTitle(todoDetails.getTitle());
        todoToUpdate.setCompleted(todoDetails.isCompleted());
        todoToUpdate.setOrder(todoDetails.getOrder());
        todoToUpdate.setUrl(todoDetails.getUrl());
        return ResponseEntity.ok(todoService.createOrUpdateTodo(todoToUpdate));
    } else {
        return ResponseEntity.notFound().build();
    }
}

删除Todo

@DeleteMapping("/{id}"):将带有路径变量id的HTTP DELETE请求映射到此方法。它通过ID删除待办事项。

ResponseEntity<Void>:如果删除成功则返回204 No Content。

1
2
3
4
5
6
@Transactional
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodoById(@PathVariable Long id) {
    todoService.deleteTodoById(id);
    return ResponseEntity.noContent().build();
}

Todo存储库

通过扩展JpaRepository,TodoRepository继承了几个用于执行常见数据库操作的方法,无需显式定义它们:

  • save(S entity):保存给定实体
  • findById(ID id):通过ID检索实体
  • findAll():检索所有实体
  • deleteById(ID id):通过ID删除实体
  • deleteAll():删除所有实体

@Repository注解表明该接口是Spring Data存储库。

1
2
3
4
5
6
7
8
9
package org.acme.todo.repository;

import org.acme.todo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
}

本地运行应用程序

最后我们定义数据库配置。要运行后端数据库,或者您可以在application.properties中取消注释H2数据库设置。(在这种情况下确保注释掉PG属性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
## 默认连接池
spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.maximumPoolSize=5

# H2
#spring.datasource.url=jdbc:h2:mem:testdb
#spring.datasource.driverClassName=org.h2.Driver
#spring.datasource.username=sa
#spring.datasource.password=password
#spring.h2.console.enabled=true
#spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
#spring.jpa.hibernate.ddl-auto=update

## PostgreSQL
spring.datasource.url=jdbc:postgresql://todos-database:5432/todos
spring.datasource.username=jws
spring.datasource.password=jws
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

要为后端启动PG数据库,我使用podman(容器运行时)

1
podman run --name todos-database -p 5432:5432 -e POSTGRES_USER=jws -e POSTGRES_PASSWORD=jws -e POSTGRES_DB=todos -d postgres:15-alpine

从源代码运行应用程序。

命令mvn spring-boot:run用于使用Apache Maven运行Spring Boot应用程序。

mvn spring-boot:run命令是一种方便的方式,可以直接从命令行启动Spring Boot应用程序,而无需先将其打包成JAR或WAR文件。它通常在开发阶段用于快速测试和迭代应用程序。

1
mvn spring-boot:run

构建镜像并推送到存储库

首先我们需要构建和打包源文件

1
mvn clean compile package

该命令由Maven构建生命周期的三个独立阶段组成:clean、compile和package。正如目标所述,一旦命令完成,它应该将WAR文件复制到target目录中。准备部署到JBoss Web Server(JWS)

JWS Operator可以使用预构建的镜像,也可以从源代码派生一个。在这个例子中,我们创建一个镜像并将其推送到quay.io

首先简单介绍一下Dockerfile

1
2
3
FROM registry.redhat.io/jboss-webserver-6/jws60-openjdk17-openshift-rhel8:6.0.2-2

COPY target/todo-demo-jws-0.0.1-SNAPSHOT.war /deployments/ROOT.war

我们使用JBoss Web Server 6镜像。镜像使用OpenJDK版本17

我们还将war文件复制到根目录,以便在/上解析。

运行以下命令构建镜像。

1
podman build --arch=x86_64 -t YOUR_REPO_NAME:latest .
  • podman build:这是使用Podman构建容器镜像的主要命令。它类似于Docker中的docker build
  • –arch=x86_64:这指定了容器镜像的架构。x86_64表示镜像应该为64位x86架构构建。当您为不同架构构建镜像并需要明确指定目标架构时,这很有用。例如,如果您在Apple Silicon(如M系列)上运行构建,这很有用
  • -t quay.io/sshaaf/todo-demo-jws:latest:例如,-t用于标签,其余部分是存储库名称/镜像

构建完成后,将镜像推送到OpenShift集群可访问的存储库。Quay.io或DockerHub都可以被OpenShift集群访问,因为它们是公开托管的。

使用Operator在OpenShift上运行应用程序

首先,在部署应用程序之前,我们需要部署一个数据库

数据库

以下命令使用最新的PostgreSQL镜像在OpenShift中创建一个新的PostgreSQL数据库应用程序。它使用用户名jws、密码jws和数据库名称todos设置数据库。应用程序名为todos-database。

1
2
3
4
5
oc new-app -e POSTGRESQL_USER=jws \
            -e POSTGRESQL_PASSWORD=jws \
            -e POSTGRESQL_DATABASE=todos \
            openshift/postgresql:latest \
            --name=todos-database
  • oc new-app:这是在OpenShift中创建新应用程序的主要命令。它有助于从源代码、模板或Docker镜像实例化新应用程序
  • -e POSTGRESQL_USER=jws:此标志设置环境变量POSTGRESQL_USER,值为jws。在此上下文中,它用于指定PostgreSQL数据库的用户名
  • -e POSTGRESQL_PASSWORD=jws:类似于前一个标志,这设置另一个环境变量POSTGRESQL_PASSWORD,值为jws。这是PostgreSQL用户的密码
  • -e POSTGRESQL_DATABASE=todos:这将环境变量POSTGRESQL_DATABASE设置为值todos,指定要创建的PostgreSQL数据库的名称
  • openshift/postgresql:latest:这部分指定用于新应用程序的Docker镜像。openshift/postgresql:latest表示应使用OpenShift存储库中最新版本的PostgreSQL镜像
  • –name=todos-database:此选项为正在创建的新应用程序分配名称。在这种情况下,应用程序将命名为todos-database

Operator

有多种安装operator的方法。Operator可通过OpenShift控制台中的OperatorHub搜索和安装

创建新的Webserver实例

安装Operator后。可以使用以下CR安装应用程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: web.servers.org/v1alpha1
kind: WebServer
metadata:
  name: todo-demo-app
  namespace: test
spec:
  webImage:
    applicationImage: 'quay.io/sshaaf/todo-demo-jws'
  applicationName: jws-app
  replicas: 2
  • apiVersion: web.servers.org/v1alpha1:这指定资源的API版本。在这种情况下,它是web.servers.org/v1alpha1,表明此资源是自定义API组web.servers.org的一部分,并且处于版本v1alpha1
  • kind: WebServer:这定义了正在描述的资源的种类。这里,它是一个WebServer。这可能是Kubernetes集群中由CRD定义的自定义资源
  • metadata:此部分包含有关资源的元数据
  • name: todo-demo-app:这将资源名称设置为todo-demo-app
  • namespace: test:这指定资源将在test命名空间中创建
  • spec:此部分定义资源的期望状态
  • webImage:此嵌套部分可能涉及Web服务器的镜像设置
  • applicationImage: 'quay.io/sshaaf/todo-demo-jws':这指定用于应用程序的Docker镜像。它指向quay.io/sshaaf/todo-demo-jws,这是一个托管的镜像存储库(Quay.io)
  • applicationName: jws-app:这将应用程序名称设置为jws-app
  • replicas: 2:这指定要运行的应用程序副本(实例)的所需数量。在这种情况下,请求了2个副本

运行以下命令以获取已部署应用程序的路由

1
oc get routes
1
2
NAME      HOST/PORT                            PATH   SERVICES   PORT    TERMINATION   WILDCARD
jws-app   jws-app-xx.apps.red.demoshift.com          jws-app    <all>                 None

您现在可以使用运行在JWS上的出色TODO应用程序,使用Spring Boot 3和AngularJS。

更多可配置选项

  • replicas:这指定要运行的应用程序副本(实例)的所需数量
  • useSessionClustering: true:启用DNSping会话集群
  • 通过Webhooks部署:JWS文档

← 上一篇 下一篇 →

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计