Kubernetes 集群

本章节中,我们介绍如何将 Jmix 应用程序部署至 k8s 集群。我们使用由 minikube 提供的单节点集群,你可以安装到开发机器上,本地测试部署。

我们主要关注为了使应用程序能运行在 k8s 中,需要做什么配置。即使没有任何 k8s 的经验,也可以通过本指南将你的应用程序准备好部署至这样的环境。但是,如果是生产环境的部署,最好还是有一些技术基础。

配置应用程序

镜像构建

Spring Boot Gradle 插件提供 bootBuildImage 任务,可以构建应用程序并创建 Docker 镜像。如需指定镜像名称,在 build.gradle 文件添加下列内容:

bootBuildImage {
    imageName = 'mycompany/sample-app'
}

Hazelcast 配置

Jmix 框架的模块使用了多种缓存:JPA 实体和查询缓存、悲观锁、动态属性配置等。在集群环境运行时,Jmix 应用程序需要在集群节点之间协调缓存。

所有 Jmix 的缓存都支持通过 Hazelcast 协调。本指南中,我们将使用 Hazelcast 的嵌入(embedded)模式,以及用于在 k8s 环境自动发现的 hazelcast-kubernetes 插件。

按照下列步骤配置 Hazelcast,支持在 k8s 集群中协调缓存。

  1. build.gradle 添加 Hazelcast 依赖:

    implementation 'com.hazelcast:hazelcast'
    implementation 'com.hazelcast:hazelcast-kubernetes:2.2.3'
  2. 通过在 application.properties 文件添加下列属性指定使用 Hazelcast 作为 JCache provider:

    spring.cache.jcache.provider = com.hazelcast.cache.HazelcastMemberCachingProvider
  3. resources 根目录创建 hazelcast.yaml 文件:

    hazelcast:
      network:
        join:
          multicast:
            enabled: false
          kubernetes:
            enabled: false
            service-name: sample-app-service

hazelcast.network.join.kubernetes.service-name 属性必须指向定义在 应用程序服务配置文件 中的应用程序服务。

注意,文件中 hazelcast.network.join.kubernetes.enabled 设置为 false。应用程序在无 k8s 的环境本地运行时,需要设置为 false。但是如果运行在 k8s 中,要求设置为 true,可以在应用程序服务配置文件中使用 HZ_NETWORK_JOIN_KUBERNETES_ENABLED 环境变量配置。

创建 Kubernetes 配置文件

在项目根目录创建 k8s 文件夹,添加下列文件。

数据库服务配置

该文件定义名称为 sample-db-service 的 PostgreSQL 数据库服务。

k8s/db.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-db-config
data:
  db_user: root
  db_password: root
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sample-db-pvclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-db
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-db
  strategy: {}
  template:
    metadata:
      labels:
        app: sample-db
    spec:
      volumes:
        - name: sample-db-storage
          persistentVolumeClaim:
            claimName: sample-db-pvclaim
      containers:
      - image: postgres
        name: sample-db
        env:
          - name: POSTGRES_USER
            valueFrom:
              configMapKeyRef:
                name: sample-db-config
                key: db_user
          - name: POSTGRES_PASSWORD
            valueFrom:
              configMapKeyRef:
                name: sample-db-config
                key: db_password
          - name: PGDATA
            value: /var/lib/postgresql/data/pgdata
          - name: POSTGRES_DB
            value: sample
        ports:
          - containerPort: 5432
            name: sample-db
        volumeMounts:
          - name: sample-db-storage
            mountPath: /var/lib/postgresql/data
---
apiVersion: v1
kind: Service
metadata:
  name: sample-db-service
spec:
  type: ClusterIP
  ports:
    - port: 5432
  selector:
    app: sample-db

应用程序服务配置

该文件定义名称为 sample-app-service 的应用程序服务。使用 mycompany/sample-app Docker 镜像

k8s/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - image: mycompany/sample-app
          imagePullPolicy: IfNotPresent
          name: sample-app
          env:
            - name: DB_USER
              valueFrom:
                configMapKeyRef:
                  name: sample-db-config
                  key: db_user
            - name: DB_PASSWORD
              valueFrom:
                configMapKeyRef:
                  name: sample-db-config
                  key: db_password
            - name: DB_HOST
              value: sample-db-service
            - name: SPRING_PROFILES_ACTIVE
              value: k8s
            - name: HZ_NETWORK_JOIN_KUBERNETES_ENABLED
              value: "true"
          lifecycle:
            preStop:
              exec:
                command: [ "sh", "-c", "sleep 10" ]
          ports:
            - containerPort: 8080
            - containerPort: 5701
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app-service
spec:
  type: NodePort
  ports:
    - port: 8080
      name: sample-app-app
    - port: 5701
      name: sample-app-hazelcast
  selector:
    app: sample-app

负载均衡配置

Jmix 用户界面 要求单个用户的所有请求都发送至同一个服务端。因此,在多服务的集群环境,需要一个粘性会话的负载均衡器。下面是带 session affinity 的 NGINX Ingress Controller 配置。

k8s/balancer.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: balancer
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/affinity-mode: "persistent"
    nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
  defaultBackend:
    service:
      name: sample-app-service
      port:
        number: 8080
  rules:
    - http:
        paths:
          - backend:
              service:
                name: sample-app-service
                port:
                  number: 8080
            path: /
            pathType: Prefix

Hazelcast 访问控制配置

k8s/hazelcast-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: hazelcast-cluster-role
rules:
  - apiGroups:
      - ""
    resources:
      - endpoints
      - pods
      - nodes
      - services
    verbs:
      - get
      - list

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: hazelcast-cluster-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: hazelcast-cluster-role
subjects:
  - kind: ServiceAccount
    name: default
    namespace: default

设置本地 Kubernetes

  1. 确保你的机器有 Docker 在运行。下面命令展示 Docker 版本:

    docker -v

    如果命令失败,参阅 Docker 文档 了解如何安装并运行 Docker。

  2. 按照 说明 安装 Minikube。

  3. 运行 Minikube:

    minikube start --vm-driver=virtualbox
  4. 启用 Ingress Controller:

    minikube addons enable ingress
  5. 配置 k8s 命令行工具使用 Minikube:

    kubectl config use-context minikube
  6. 在浏览器打开 k8s 仪表板,在一个单独的终端运行:

    minikube dashboard

构建运行应用程序

  1. 构建 Docker 镜像:

    ./gradlew bootBuildImage
  2. 加载镜像至 Minikube:

    minikube image load mycompany/sample-app:latest
  3. 应用 k8s 配置文件

    kubectl apply -f ./k8s
  4. 如需增加应用程序实例数量,使用下列命令:

    kubectl scale deployment sample-app --replicas=2
  5. 查找集群 IP 地址:

    minikube ip

    使用这个地址在浏览器打开应用程序,示例:http://192.168.99.100