SkaffoldとHelmを使い環境の設定を切り替えてk8sリソースをデプロイする

はじめに

以前のブログでSkaffoldのローカルでの開発機能を試しました。もちろんSkaffoldはローカルでの開発をサポートするツールにとどまらず、テストやビルド、デプロイなどもサポートしています。
デプロイをおこない場合は環境ごとの変数をうまく切り替える必要があると思いますが、SkaffoldはHelmのサポートを行っているのでそいつを使えばうまくできそうだったので試してみようかと思います。
SkaffoldはProfilesという機能を持っておりコンテキストごとのデプロイ、テスト、ビルドを切り替えることができますが、今回はその機能は使わずにTemplated Fieldsの機能を使ってやってみたいと思います。

やってみる

Java/Springのアプリケーション作ってやってみようと思います。
ローカルのクラスターはMinikube(Docker Driver)を使って作成します。

環境

今回の実行環境は以下の通りです。

$ uname -srvmpio
Linux 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal

$ minikube version
minikube version: v1.22.0
commit: a03fbcf166e6f74ef224d4a63be4277d017bb62e

$ kubectl version -o yaml
clientVersion:
  buildDate: "2021-03-18T01:10:43Z"
  compiler: gc
  gitCommit: 6b1d87acf3c8253c123756b9e61dac642678305f
  gitTreeState: clean
  gitVersion: v1.20.5
  goVersion: go1.15.8
  major: "1"
  minor: "20"
  platform: linux/amd64
serverVersion:
  buildDate: "2021-06-16T12:53:14Z"
  compiler: gc
  gitCommit: 092fbfbf53427de67cac1e9fa54aaa09a28371d7
  gitTreeState: clean
  gitVersion: v1.21.2
  goVersion: go1.16.5
  major: "1"
  minor: "21"
  platform: linux/amd64

$ skaffold version
v1.29.0

Springアプリケーションの作成

今回はアプリケーションが環境ごとの変数の文字列を読み取ってリクエスト側に返すようなアプリケーションを作成します。
Spring Initializrで以下の設定でアプリを作成します。

f:id:yuya_hirooka:20210807143322p:plain

ダウンロードしたZipを適当なIDEなどで開いて、SkaffoldDeployApplicationを編集し以下の用にコントローラーを作成しプロパティファイルから読み込んだ値を返す用に指定しておきます。

@SpringBootApplication
@RestController
@PropertySource("classpath:application.properties")
public class SkaffoldDeployApplication {

    @Value("${skaffold.env}")
    private String env;

    @GetMapping("/envval")
    public String env() {
        return env;
    }

    public static void main(String[] args) {
        SpringApplication.run(SkaffoldDeployApplication.class, args);
    }
}

読み込むプロパティをapplication.proertiesに記述します。

skaffold.env=test

デフォルトではdevの文字列が変えるようになりますが、Spring Bootではこの値を環境変数SKAFFOLD_ENVで上書きすることができます。(変数の上書き順序に関してはこちらを確認してください’)

アプリケーションを起動して、cURLを叩いてみます。

$ mvn spring-boot:run

$ curl localhost:8080/envval
test

デフォルトの文字列であるdevが返ってきますね。

SkaffoldとHelmを初期化する

プロジェクトルートで、以下のコマンドでSkaffoldの初期化を行います。

$ skaffold init -k helm
apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-deploy
deploy:
  kubectl:
    manifests:
    - helm

? Do you want to write this configuration to skaffold.yaml? Yes
Configuration skaffold.yaml was written
You can now run [skaffold build] to build the artifacts
or [skaffold run] to build and deploy
or [skaffold dev] to enter development mode, with auto-redeploy

この際にSkkafoldはマニフェストの位置を指定してやる必要があるため-kオプションで指定します。
このディレクトリは存在する必要なないので、とりあえず作りたい場合は適当に埋めておきます。

次にHelmの初期化を行います。
プロジェクトのルートで、以下のコマンドを実行し初期化します。

$ helm create helm
Creating helm

すると以下のようなファイルが作成されます。

$ tree helm
helm
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

不要なファイルもいくつかありますが、今はは一旦そのままにして先に進みます。

Helmのマニフェストを書き換える

helm createでできあがったテンプレートを書き換えたり不要なファイルを削除したりします。

今回はServiceとDeploymentテンプレートのみで構成する簡単な環境を構築しようと思います。
ingress.yamlserviceaccount.yamltest/test-connection.yaml_helpers.tplNOTES.txtを削除します。

次に、deployment.yamlのテンプレートを以下のように書き換えます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
  labels:
    app: spring-app
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      containers:
        - name: spring-app
          image: {{ .Values.image }}
          ports:
            - name: http
              containerPort: {{ .Values.app.port }}
              protocol: TCP
          env:
          - name: SKAFFOLD_ENV
            value: {{ .Values.app.env }}
          - name: SERVER_PORT
            value: {{ .Values.app.port }}

変数としてSKAFFOLD_ENVとPod数、イメージを変えられるように設定しています。 同じようにservice.yamlも書き換えます。

apiVersion: v1
kind: Service
metadata:
  name: spring-app
  labels:
    app: spring-app
spec:
  type: ClusterIP
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.app.port }}
      protocol: TCP
      name: http
  selector:
    app: spring-app

ここではポートだけがDevelopmentの方の設定と同じ用になるように設定しています。
次にテンプレートに対するで、デベロップメントと用とプロダクション用の2種類のvalues.yamlを用意します。

まずは、デベロップメントようのvalues-dev.yamlです

replicaCount: 2

image: spring-app

app:
  port: 8081
  env: dev

service:
  type: NodePort
  port: 8081

Podのレプリカ数を2に設定し、環境をdevで設定しています。
imageはSkaffoldでビルドするイメージを使うようにしておきます。 また、このブログではあまり重要ではありませんが、デベロップメント環境へのデプロイということでServiceのtypeもNodePortにしています。

次に、プロダクション用のvalues-prod.yamlを用意します。

replicaCount: 4

image: spring-app

app:
  port: 8081
  env: prod

service:
  type: ClusterIP
  port: 8081

先程のデベロップメントとの違いでいえば、レプリカ数を4に変え、環境をprodで指定しています。
また、こちらも重要ではありませんが、プロダクション環境へのデプロイということでServiceのtypeはClusterIPにしています。

Skaffold側でHelmを使うように設定する

Helmの方の設定が終わったのでSkaffoldから利用する設定を記述します。
skaffold.yamlを以下のように書き換えます。

apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-deploy
build:
  artifacts:
    - image: spring-app
      buildpacks:
        builder: gcr.io/buildpacks/builder:v1
deploy:
  helm:
    releases:
      - name: spring-app
        namespace: default
        artifactOverrides:
          image: spring-app
        chartPath: helm
        valuesFiles:
          - "{{ .VALUES_FILE }}"
portForward:
- resourceType: service
  resourceName: spring-app
  port: 8081

buildディレクティブではBuildpacksを使ってアプリのBuildを行っています。
ここでのイメージ名を先程のvalues-*.yamlで書いた値と合わせておきます。
今回はローカルでビルドしたイメージを使うためこのような構成にしていますが、本来的にはDockerりぽじとりを使うことになると思います。

次に、deployディレクティブですがHelmの設定を行っています。注目すべきはchartPathvaluesFiles部分でそれぞれHelm ChartとValuesファイルの置き場所を指します。
valuesFilesには"{{ .VALUES_FILE }}"という記述をしており、これはSkaffoldのTemplated Fieldsという機能を利用しています。 これで環境変数VALUES_FILEで指定されるvalue-*.yamlが実際のデプロイ時に使われるようになります。

最後のportForwardはk8sのPort Fowordの設定です。書いてあるとおりですが、名前がspring-appであるServiceに対して8081:8081でPort Forwordを行います。

これでプロジェクトの作成と諸々の設定は完了です。

アプリをデプロイする

それでは、アプリをデプロイしていきます。
Skaffoldにはdeployコマンドや、buildとdeployを合わせたrunコマンドなどがありますが、今回は検証のためにPort Forwordを行いたいためdevコマンドを使います。
まずは、デベロップメントを想定したデプロイです。
以下のコマンドを実行します。

$ VALUES_FILE=./helm/values-dev.yaml skaffold dev

初回起動に時間がかかりますが、起動すれば、アプリケーションのログが流れ始めます。
これでvalues-dev.yamlがテンプレートに反映されたリソースがデプロイされているはずです。
cURLでリクエストを送ったり、Podの数を確認したりしてみましょう。

$ curl localhost:9000/envval
dev

$ kubectl get deployment/spring-app 
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
spring-app   2/2     2            2           45m

Podが2個起動され、cURLdevの値が返ってきているので、想定通りですね。

次にプロダクションを想定したデプロイです。

$ VALUES_FILE=./helm/values-prod.yaml skaffold dev

アプリケーションログが流れ始めたら。 先ほどと同じように確認してみます。

$ curl localhost:8081/envval
prod

$ kubectl get deployment 
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
spring-app   4/4     4            4           114s

本番用の設定でデプロイされていますね。