Kubernetes嫌いなエンジニアが実際に仕事でKubernetesを使ってみて心境がどう変わったか


皆さん、こんにちは。
今回の記事では、筆者が今まで嫌悪感を抱いていたKubernetes(以下、k8s)について、直近の仕事でk8sを使う案件に入りその作業も一通りの山場を超えたので、今回はあれだけk8sを嫌っていた筆者が実際に使ってみてどうだったかというのを記事にしたいと思います。

## Kubernetesの概要

k8sはコンテナ化されたアプリケーションのデプロイ、スケーリングなどの管理を自動化するためのプラットフォーム(コンテナオーケストレーションエンジン)です。
コンテナランタイムの一つであるDockerは、Docker単体では複数のホストで構成一定規模以上のプロダクションに利用に耐えうるシステムを構築するのは難しく、今日ではそのようなシステムを構築するにはk8sに代表されるコンテナオーケストレーションエンジンを利用することが一般的です。[1]
k8sのアーキテクチャについては、[クラスタのアーキテクチャ](https://kubernetes.io/ja/docs/concepts/architecture/)を御覧ください。

尚、今回の案件ではVhostベースの複数のWebサービスをコンテナで運用することを目的とし、k8sを利用すると言った案件でした。

## 何故Kubernetesが嫌いなのか

さて、筆者が何故k8sを嫌っているのかを説明します。因みに、k8sを批判するようなものではないこととは最初にお約束します。

1. ~~そもそも、k8sを使う恩恵を存分に受けられるような案件に携わる事がなかったから~~

筆者のこれまでのキャリアでは、AWSやGCPと言ったクラウドシステム上で運用しているWebサービスの三層アーキテクチャを主に取り扱う事が主でした。筆者は、主に3層の運用・Appサーバの役割を担うRails開発・また、クラウドサービスを取り扱い、インフラアーキテクチャを構築・運用するような案件が多かった印象です。

![図1: アーキテクチャの概要](https://storage.googleapis.com/zenn-user-upload/7104b82a849f-20220404.png)

また、Webサーバ・Appサーバに関しては、Virtual Machine(以下、VM)で構築したものを取り扱っていました。このアーキテクチャでは、サービスがスパイクした時に柔軟に対応できるように一応、スケールアウトの設定を入れていましたが、中々急激にVMのリソースを消費するようなトラフィックの増大するようなサービスでもなかった事もあり、筆者はこれまで大規模なサービスの開発・運用に携わった事がない状態でした。
そんな筆者がコンテナオーケストレーションシステムを使うと言っても、大規模なサービスに関わった事がない人間が使っても、宝の持ち腐れになるんじゃないかと考えていました。
今のアーキテクチャで充分に対応できているのに、k8sを態々導入して、コンテナオーケストレーションシステムを使った事による恩恵が受けられるのでしょうか。
だったら、その案件に携わる準備として、k8sの学習用のツールとしてminikubeを使って試験的運用するのもアリかと思われますが、前述の仮に、コンテナオーケストレーションシステムを導入するにあたっての準備として使うのであれば、最初からk8sを使って学習した方が良いのでは？と考えていました(例えて言うなら、ギター弾くのがうまくなりたいのであれば、ギター弾くのが良いのであって、指を動かす運動をするのではないみたいな)。

2. リソースを限界まで使わないエコシステムという考え方が、そんなに好きじゃない

こちらはk8sどうこうと言う理由よりも、仮想化技術の目的である`サーバのリソースを効率的に扱うこと`に対しての筆者の考え方です。
昨今ITに限った話でもなく、色々な情勢の変化もあり、限られた資源を大事に使う、節約・倹約と言った考え方が非常に大事になっているかと思われます。
勿論、この考え方自体は非常に素晴らしいものだと考えています。筆者もしばらくはこのような仕事に携わる事も多くありました。
しかし、実務経験として実際にやってみた時の筆者の考え方が、どんどんケチくさくなっていたという事にふと気がついてしまったのです。
筆者のモチベーションが上がる一因として、イノベーションあるサービスを作る事、及びそのアイデアを生み出すための活動を続けてまいりました。その上で上記のようなエコシステムという考え方に関しては、`それ自体は、ユーザを驚かせるようなサービスを生めないんじゃないか？`と考えています(クラウドシステムも、クラウド上に構築されているサービスが面白いのであって、クラウド基盤自体が面白いと感じたことはなかった)。

以上の上記２つの理由があって、k8sに対しての苦手意識を持っていた理由になります。この内、1. については、その案件に今回関わるようになったので、その理由自体は取り敢えずなくなった印象です。

## 案件内容

今回の案件内容は、名前ベースのバーチャルホスティングによって、複数のWebシステムを同一クラスタ上に実現する作業です。尚、Webシステムの前提条件としては以下のものがあります。

1. k8s上に搭載されるWebシステムは開発環境・あるいは社内サービスの本番環境である。
2. そのため、負荷分散はそれほど考えなくてもよい。
3. k8sのEngineはGKEを使う。
4. IAPを使いたい。

上記３つの前提条件の内、1., 2.を考慮した上でレプリカPod数は1の最低、1台のPodはNode上で動いているように設定します。

なお、名前ベースのバーチャルホスティングのインフラアーキテクチャのポンチ絵については、[2]のものが詳しいと思うので、詳しくは[2]の文献も参照して下さい。

また、k8s上で運用するWebシステムは図1のような、リバースプロキシWebサーバ + アプリケーションサーバのコンテナ構成で行っています。

.4については、社内サービスでもあるため、VPNではなくリソースレベルで社内関係者のみがアクセスできるようにするため、この設定は必須となりました。
GKEのロードバランサ(Ingress)には、L4とL7の二種類があり、残念ながらL4タイプはIAPをサポートしておらず[2]、L7タイプのIngressによって、GKEクラスタを構成する必要がありました。

## Ingressの設定

```yaml:ingress.yaml
# Ingress Resource
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: *****-dev-services
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "*****-services-ingress-static-ip"
spec:
  tls:
  - secretName: *****-services-tls
  - secretName: *****-biz-tls
  rules:
  - host: *****.*****-dev.com
    http:
      paths:
      - path: "/"
        pathType: Prefix
        backend:
          service:
            name: *****-svc
            port:
              number: 80
  - host: *****.*****-dev.com
    http:
      paths:
      - path: "/"
        pathType: Prefix
        backend:
          service:
            name: *****-svc
            port:
              number: 80
  - host: *****.*****-inc.biz
    http:
      paths:
      - path: "/"
        pathType: Prefix
        backend:
          service:
            name: *****-svc
            port:
              number: 80
```

上記がIngressの設定に対してのyamlファイルになります。上記の設定では、ロードバランサに対して`Hostヘッダ`に基づいてリクエストを転送するように指示しています。[3]

各ホストに対して、Podの集合で実行されているアプリケーションをネットワークサービスとして公開する`Service`を作成し、各ホストに応じたServiceを利用するように記述しています。[4]

## Kustomizeによるファイル構成で環境差分を管理する

当初は、k8sに出てくる登場人物、設定方法に慣れるため、k8sに関係するリソースは全て手書きで書いていました。しかし、ある程度クラスタの中に立ち上げるWebシステムが増加していくにつれ、異なるWebサービスとは言え、k8s上に実現する上で書いていく設定が冗長になっていき、リポジトリ管理しているとはいえ、煩雑になっていきました。
そこで、Kustomizeを利用し、。Webシステムの環境差分を管理する方針を取りました。
Kustomizeでは、ベースとなるマニフェストと、そのマニフェストに対する環境差分を管理し、ベースのマニフェストに対して環境差分情報のパッチを当てるという形でデプロイするマニフェストを生成、管理します。[5]

Kustomizeは、k8s 1.14からkubectlに統合され、`kubectl apply -k <ディレクトリ>` でKustomizeで構成したファイル群でK8sのインフラリソースのデプロイが可能となりました。[6]

以下は、Kustomizeによって修正したファイル構成になります。

```
% tree
.
├── base
│   ├── backendconfig.yaml
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   ├── nginx-health-config.yaml
│   └── service.yaml
├── ingress.yaml
├── kustomization.yaml
└── overlays
    ├── app1
    │   ├── deployment.yaml
    │   ├── kustomization.yaml
    │   ├── secret.env
    │   ├── secret.env.enc
    │   └── service.yaml
    ├── app2
    │   ├── deployment.yaml
    │   ├── kustomization.yaml
    │   ├── secret.env
    │   ├── secret.env.enc
    │   └── service.yaml
    └── app3
        ├── deployment.yaml
        ├── kustomization.yaml
        ├── secret.env
        ├── secret.env.enc
        └── service.yaml

5 directories, 22 files
```

大元のIngressに関しては、環境毎の差分を作るのは難しかったため、前述のIngress.yamlの状態のまま設定しています。また、トップディレクトリのkustomization.yamlの設定は以下のようになっています。

```yaml:kustomization.yaml
resources:
  - ingress.yaml
  - overlays/app1
  - overlays/app2
  - overlays/app3
```

これにより、リバースプロキシ用のNginxの設定、IngressにIAPの構成情報を紐付けるためのBackendConfig、Deployment、Serviceなどの共通のパターンをbaseディレクトリで管理し、環境毎に差分が出た設定はAppディレクトリ配下で管理するような構成を取ることができました。

ここでoverlays/app1のkustomization.yamlの中身を見てみましょう。

```yaml:overlays/app1/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md#prefixsuffix-transformer
# 全てのリソースにapp1-というプレフィックス名を付与する
namePrefix: app1-
nameSpace: *****

# https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md#labels-transformer
# プロジェクト内の全てのリソースに宣言したラベルを追加する。
commonLabels:
  app: app1

resources:
  - ../../base

patches:
  - deployment.yaml
  - service.yaml

configMapGenerator:
  - name: env-config
    literals:
      - (中略)

secretGenerator:
  - name: env-secret
    envs:
    - secret.env
```

commonLabelsによって、プロジェクト内で宣言された全てのリソースにラベルを設定できます。[7]
commonLabelsによって、PodTemplatesのラベルセレクタに適用され、プロジェクト内のリソースを識別することができます。
また、configMapGenerator, secretGeneratorなどを用いてサービスを動かすために必要なConfigやSecretデータをリテラル、もしくはファイルから生成することができます。[8]
今回のプロジェクトでは、Config関係はリテラルに生書きしてConfigMapリソースを生成、Secret関係は予め、KMSで生成した鍵を用いて暗号化したファイルだけリポジトリにコミットし、復号化した平文データを用いて、Secretリソースを生成するようにしています。

patchesやresourcesを用いて、サービスを動かすために必要なリソースをbaseとするファイルから生成、個々の差分を取り込む必要があるものはpatchesで設定をオーバーライドするようにしています。

ちょっと大分、駆け足でしたが、kustomizeを用いる事によって、煩雑になりがちなファイル群の整理もすることが出来ました。

## 実際、使ってみてKubernetesはどうだったのか

上手く纏まっているか分からないですが、筆者としては、新たにインフラの設定を効率よく運用できそうなツールを手に入れたくらいの気持ちで考えています。
元々、k8sが嫌いな理由を一旦整理してみたのですが、改めて振り返ってみると、実はk8sどうこうというより、そもそもエコシステムに嫌悪感を抱き始めているという思いの方が強いので、正直、筆者の我儘過ぎる持論が大きく出てしまったんだなという反省に落ち着きそうです。

それが良いことなのか悪いことなのかは置いておいて、少なくともこれからも必要になった時はk8sを使うのもアリかもくらいに留めておきます。

それでは。

## 参考文献

[1] 青山真也 (2020). Kubernetes完全ガイド 第２版 株式会社インプレス
[2] https://github.com/kubeflow/manifests/issues/1161
[3] https://kubernetes.io/ja/docs/concepts/services-networking/ingress/#%E5%90%8D%E5%89%8D%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%83%9B%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0
[4] https://kubernetes.io/ja/docs/concepts/services-networking/service/
[5] https://atmarkit.itmedia.co.jp/ait/articles/2101/21/news004.html
[6] https://qiita.com/MahoTakara/items/79e626ea927ded25af62
[7] https://kubectl-book-ja.netlify.app/pages/app_management/labels_and_annotations.html
[8] https://kubectl-book-ja.netlify.app/pages/app_management/secrets_and_configmaps.html