Tagbangers Blog

Spring Cloud Kubernetes を試してみる

はじめに

Spring Cloud Kubernetes は Kubernetes ネイティブ API を使用した Spring Cloud の共通インターフェイスの実装で、次の 4 つの Spring Boot Starter がある。

spring-cloud-starter-kubernetes:
Kubernetes Service からサービス名を解決する Discovery Client の実装。

spring-cloud-starter-kubernetes-config:
Kubernetes ConfigMapSecret から application properties を読み込む。
ConfigMap または Secret が変更されたときに application properties をリロードすることも可能。

spring-cloud-starter-kubernetes-ribbon:
Kubernetes Endpoint からサーバリストを取得するクライアントサイドロードバランサー(Ribbon)。

spring-cloud-starter-kubernetes-all:
上記すべてを含む Starter

今回は、Playing with Sping Boot on Kubernetes を参考に Kubernetes の世界観に触れつつ、Spring Cloud Kubernetes を使ってみる。
使用するのは上記 4 つの Starter のうち spring-cloud-starter-kubernetes-config のみ

公式ドキュメントをみて、Kubernetes のアーキテクチャや登場するオブジェクトの名前はおさえておく。
https://kubernetes.io/docs/concepts/

環境

Mac OS X

1. ローカル環境に Minikube を構築する

https://kubernetes.io/docs/tasks/tools/install-minikube

仮想化が Mac OS で有効になっているかをチェック。

sysctl -a | grep -E --color 'machdep.cpu.features|VMX'

VMX が赤字で出力されれば VT-x 機能が有効になっているので OK。

kubectl のインストール
https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-macos

brew install kubernetes-cli

VirtualBox のインストール

brew cask install virtualbox

Minikube のインストール

brew cask install minikube

Minikube の起動と Cluster の作成

minikube start

ダッシュボードを見る

minikube dashboard


2. アプリケーションを作る

Spring Initializr でひな形を作成する

spring init \
    --dependencies=data-jpa,data-rest,actuator,h2,devtools,lombok \
    --groupId=sample \
    --artifactId=spring-k8s-sample \
    --boot-version=2.2.0.BUILD-SNAPSHOT \
    --name=Sample \
    --package-name=sample \
    spring-k8s-sample

NOTE: spring init --list でサービスが一覧できる

IntelliJ IDEA でプロジェクトを開く

idea spring-k8s-sample/

NOTE: コマンドラインから IntelliJ IDEA を開くためには事前に Tools > Create Command-line Launcher を設定しておく。

Entity を作る

src/main/java/sample/Customer.java

@Entity
@Data
public class Customer {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    private String name;
    private String email;
 
}

Repository を作る

src/main/java/sample/CustomerRepository.java

public interface CustomerRepository extends JpaRepository<Customer, UUID> {
}

Spring Boot を起動する

./mvnw spring-boot:run

Customer を登録してみる

curl -X POST http://localhost:8080/customers \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "東方仗助",
        "email": "jojo@example.com"
    }'

Customer を一覧してみる

curl -X GET http://localhost:8080/customers

結果

{
  "_embedded" : {
    "customers" : [ {
      "name" : "東方仗助",
      "email" : "jojo@example.com",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/ed063906-e072-471f-b903-c15dd7ca72d6"
        },
        "user" : {
          "href" : "http://localhost:8080/users/ed063906-e072-471f-b903-c15dd7ca72d6"
        }
      }
    } ]
  },
  ...SKIP...
}

3. PostgreSQL の構築

Minikube で作成した Kubernetes Cluster 上に PostgreSQL を構築してみる。

ConfigMap を作成する

kubectl create configmap postgres-config \
    --from-literal=postgres.service.name=postgresql \
    --from-literal=postgres.db.name=sample

確認する

kubectl get cm postgres-config -o json

Secret を作成する

kubectl create secret generic db-security \
    --from-literal=db.user.name=sample \
    --from-literal=db.user.password=password

確認する

kubectl get secret db-security -o json

PostgreSQL をデプロイする

PostgreSQL をデプロイするための Kubernetes リソースファイルを作成する。

src/k8s/postgres.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: postgresql
  namespace: default
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: postgresql
    spec:
      volumes:
        - name: data
          emptyDir: {}
      containers:
        - name: postgres
          image: postgres:9.6.5
          env:
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: db-security
                  key: db.user.name
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-security
                  key: db.user.password
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: postgres.db.name
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/
---
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: default
spec:
  selector:
    app: postgresql
  ports:
    - port: 5432

NOTE: 先に作成した ConfigMap と Secret からデータベース名やユーザ名などを取得する。

NOTE: IntelliJ IDEA で Kubernetes のリソースファイルを変数するときには IntelliJ IDEA の Kubernetes プラグインを入れておくと便利。

実行する

kubectl create -f src/k8s/postgres.yml

作成された Deployment を確認する

kubectl get deployment postgresql -o json

作成された Service を確認する

kubectl get service postgresql -o json

4. アプリケーションを修正する

データベースを H2 から構築した PostgreSQL に接続するように修正する。

Spring Cloud Kubernetes を導入する

pom.xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.BUILD-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>

PostgreSQL の JDBC ドライバーを導入する

pom.xml

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

application.properties を修正する

spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:postgresql://${${POSTGRES_SERVICE}.service.host}:${${POSTGRES_SERVICE}.service.port}/${POSTGRES_DB_NAME}
spring.datasource.username=${POSTGRES_DB_USER}
spring.datasource.password=${POSTGRES_DB_PASSWORD}

ややこしい設定だが、内側のプレースホルダーは ConfigMap から提供される環境変数によって解決され、外側のプレースホルダーは Kubernetes によって解決されることになる。

application.properties を ConfigMap に登録する

kubectl create configmap app-config \
    --from-file=src/main/resources/application.properties

bootstrap.properties を作成する

src/main/resources/bootstrap.properties

spring.cloud.kubernetes.config.name=app-config

これでアプリケーションが Kubernetes にデプロイされた時に、app-config として ConfigMap に保存された application.properties が使われるようになる。

5. アプリケーションをデプロイ

fabric8-maven-plugin を導入する

fabric8-maven-plugin (以降 fmp)は次のことを Maven のビルドプロセスで実行する。

  1. Docker イメージの作成(Dockerfile も自動生成する)
  2. Kubernetes および OpenShift のリソースファイルの作成
  3. Kubernetes および OpenShift へのアプリケーションのデプロイ

pom.xml

<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>fabric8-maven-plugin</artifactId>
    <version>4.2.0</version>
    <configuration>
        <enricher>
            <config>
                <fmp-service>
                    <type>LoadBalancer</type>
                </fmp-service>
            </config>
        </enricher>
    </configuration>
    <executions>
        <execution>
            <id>fmp</id>
            <goals>
                <goal>resource</goal>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Service Account を作成する

Minikube v0.26.0 から RBAC がデフォルトで有効になっているため、アプリケーションから ConfigMap が取得できるように Service Account を作成する。

Service Account の Kubernetes リソースファイルを作成する

src/k8s/sa.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-reader
  namespace: default

Role の Kubernetes リソースファイルを作成する

src/k8s/role.yml

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods","configmaps"]
    verbs: ["get", "watch", "list"]

Role Binding の Kubernetes リソースファイルを作成する

src/k8s/rb.yml

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-reader
subjects:
  - kind: ServiceAccount
    name: config-reader
    namespace: default

実行する

kubectl create -f src/k8s/sa.yml
kubectl create -f src/k8s/role.yml
kubectl create -f src/k8s/rb.yml

アプリケーションのリソースファイルを作成する

ConfigMap からデータベースの接続情報を取得して環境変数にセットする。

src/main/fabric8/app.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${project.artifactId}
  namespace: default
spec:
  template:
    spec:
      containers:
        - name: ${project.artifactId}
          env:
            - name: POSTGRES_SERVICE
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: postgres.service.name
            - name: POSTGRES_DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: postgres-config
                  key: postgres.db.name
            - name: POSTGRES_DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-security
                  key: db.user.name
            - name: POSTGRES_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-security
                  key: db.user.password
      serviceAccountName: config-reader

ビルドする

ビルドした Docker イメージを Minikube から使用できるようにする。

eval $(minikube docker-env)

Maven コマンドの実行

./mvnw clean install

fmp が Maven ビルド時に Dockerfile と Kubernetes のリソースファイルの作成を行う。

target/
├── classes
│   ├── META-INF
│   │   └── fabric8
│   │       ├── kubernetes
│   │       │   ├── spring-k8s-sample-deployment.yml
│   │       │   └── spring-k8s-sample-service.yml
│   │       ├── kubernetes.yml
...SKIP...
├── docker
│   └── sample
│       └── spring-k8s-sample
│           └── latest
│               ├── build
│               │   ├── Dockerfile

NOTE: fmp は Spring Actuator を検出して LivenessProbe と ReadinessProbe を自動設定する。

デプロイする

./mvnw clean install fabric8:apply -DskipTests=true

エンドポイントを確認する

minikube service spring-k8s-sample --url

Customer を登録してみる

curl -X POST http://192.168.99.100:30223/customers \
    -H 'Content-Type: application/json' \
    -d '{
        "name": "東方仗助",
        "email": "jojo@example.com"
    }'

Customer を一覧してみる

curl -X GET http://192.168.99.100:30223/customers

データベースの中身を確認してみる

kubectl exec -it postgresql-5dd8d89d89-p7pdl /bin/bash
psql -Usample sample
select * from customer;
                  id                  |      email       |   name  
--------------------------------------+------------------+----------
 9a34ef06-1027-45f7-88a8-2c09d957a467 | jojo@example.com | 東方仗助
(1 row)
\q
exit

おわり★