メモ - RyuSA

技術的なメモ書きとか試してみたこと

Raspberry Pi上にWi-Fi APを自動構築するKubernetes Operatorを作った話

元ネタ、というよりWhy/作ったモチベはこちらに書いてあります👀

eng-blog.iij.ad.jp

雑に言えば「自宅にメッシュWi-Fiを導入したかったので、Raspberry PiWi-Fi APを実装してみた」という感じ。

TL;DR

最終形として、こんなOperatorを作成しました。

f:id:RyuSA:20210702181459p:plain

github.com

これによりCRDであるAccessPointリソースをKubernetesにデプロイすることで、KubernetesにjoinしているRaspberry Pi上に適切に設定されたWi-Fiアクセスポイントを作成することができるようになりました。

これでkubectlひとつでメッシュなWi-Fiを構築したり来客向けのゲストWi-Fiを用意することができるようになりました、やったぜ✨

Rapsberry PiのAP化 #とは

Raspberry PiWi-FiのAPとして動かせる話は昔からあり、いろんな方が紹介してくださってます🥰

qiita.com

qiita.com

つまるところRaspberry Piのワイヤレスのネットワークインタフェースとイーサネットのネットワークインタフェースをブリッジでつなぎ、ワイヤレスネットワークインタフェースをAPモードで起動しようということですね。ソフトウェアとしてはhostapdがよく使われているみたいです🤔

Raspberry Pi自体は安価に調達できて、かつPoE Hatを利用することでLANケーブル一本で動かすことができるなど取り回しが良さそう……今回やりたいことにピッタリ。

hostapd

hostapdはネットワークインターフェースカードをアクセスポイントとして動かすためのソフトウェアで、設定ファイルを咬まして起動することで指定したSSID/パスワード/その他設定のアクセスポイントを起動することができます。すごく便利👏

とりあえず手動オペレーション💪

一度、手動でWi-Fi APをRaspberry Pi上に展開するための手順を確認してみます。

  1. ネットワーク内にDHCPを用意する
  2. Raspberry Piのネットワークインタフェースを変更する(ブリッジ作ったり)
  3. Raspberry Piにhostapdをインストール、デーモンとして登録する
  4. configファイルを作成する
  5. configファイルにSSIDとパスワード、その他設定を入れる
  6. デーモンのリロード&再起動

手動オペレーションの場合、この手順をAPにしたいRaspberry Piすべてに実施していくことになります。実際に自宅に転がってたRaspberry Pi上でオペレーションしてみたところ、1つあたり新規作成に1時間程度時間がかかりました。これで運用するのは……かなりいやですね🤔

hostapdのインストールの手順そのものはコンテナを配布することで解決自体はできます。ついでだったのでコンテナイメージを作りました

github.com

しかし、コンテナを起動したりconfigファイルを配布したりと運用作業を真心込めて1台1台手作業するのはちょっとありえないという気持ち🤮

Operatorにしていく🦾

ルーチンな運用作業はKubernetesのOperatorにするに限る。ということでOperatorで実装することを決意しました。

Kubernetes Operatorとは、KubernetesAPIリソースを監視して「現在の状況」と「理想の状況」を計算し、現在の状況をあるべき姿にドリブンしていくアプリケーションのことを指します。通常、後述するCRDと共に実装を行い日々の運用の自動化などを行うことが多いです。

有名なOperatorとしてはPrometheusのデプロイや設定をKubernetesリソースで行えるようにしたPrometheus Operatorや、RabbitMQのクラスターデプロイをKubernetesリソースで定義できるようにしたRabbitMQ Cluster Operatorなどがあります。

github.com

github.com

スコープ決め

手動オペレーションの各タスクのうち、自動化できそうな範囲を考えてみます。

  1. ネットワーク内にDHCPを用意する
    • これは各ネットワーク内で決定するべき事柄、ここまで責任を持つのは過剰
    • → スコープ外
  2. Raspberry Piのネットワークインタフェースを変更する
    • これはcloud-initを利用して自動化(=k8s関係ない範囲)
    • → スコープ外
  3. Raspberry Piにhostapdをインストール、デーモンとして登録する
    • 指定したノードにPodとして起動すれば……?
    • → DaemonSetとして配布します
  4. configファイルを作成する
    • 最低限の設定を外から渡せればConfigMapに入れるところまで自動化できそう
    • → CRDとして定義し、そこからConfigMapを作成&DaemonSetと一緒に配布します
  5. configファイルにSSIDとパスワード、その他設定を入れる
    • 4.と同様
  6. デーモンのリロード&再起動
    • Kubernetesの機能をそのまま利用します
    • → 実装しない(DaemonSetに任せる)

ということで、1.と2.以外のタスクを自動化する方針で実装していきます。

Kubebuilder

Operatorを実装するにあたり、ほしいのは"Kubernetesのエコシステム"であってOperatorのフルスクラッチではないです🤔

Kubernetes Operatorを実装するためのフレームワークとして有名なものが2つあります。

  • Kubebuilder
  • Operator SDK

それぞれを触ってみた感じ、個人的にはKubebuilderの方が使いやすそうに感じたので今回はKubebuilderを利用して実装していこうと思います🥰

こちら参考書籍です。KubernetesのOperatorとはなにか、KubebuilderとOperator SDKの違いとサンプル実装と非常にわかりやすく解説されててマジで助かりました。

CRD

KubernetesにはCuston Resource Definitionというリソースの拡張機能が存在しており、いわば「俺専用のKubernetesリソースを作ってやるぜ〜〜^^」ができます。CRD自体はKubernetes 1.16でGAとなっているAPIです。

今回のパターンでは2種類のCRDを実装しました。

  • AccessPoint
    • アクセスポイントそのものを表すリソース
    • SSIDの設定やパスワード、あと細かい設定とかと記述する
  • AccessPointDevice
    • アクセスポイントをデプロイする仮想的なデバイス
    • ブリッジの設定や操作するネットワークインタフェースなどを記述する
    • 用途としては"Aliceの部屋"とか"1F リビング"とかの単位で書いていく

ストーリーとしては

  • まずユーザーがAccessPointDeviceをデプロイ
  • ユーザーがAccessPointDeviceを指定したAccessPointをデプロイ
  • AccessPoint OperatorがAccessPointの作成イベントを受信
  • AccessPointDeviceと合わせてAccessPoint Operatorが適切なDaemonSetを作成してデプロイ
  • DaemonSetの力で各Raspberry Pi上にhostapdの設定とhostapdコンテナがデプロイされていく

……といった流れでリソースを操作していくかな?と考えています。

最終的に出来上がったもの

開発そのものについては(ただ実装しただけなので)割愛します。興味がある人が多そうであればどこかに書くなりしておきます。

最初にも紹介しましたが、こんな感じに完成しました。細かい使い方等は一応READMEに書いてあります。なお現時点ですでに可愛いバグ(AccessPointDevice消してもDaemonSetが消えない等)があったりするのは許してください。。。

f:id:RyuSA:20210702181459p:plain

github.com

開発の環境としては使い捨てできるKubernetesを作成するためのkindを利用しました。便利ですね〜

github.com

以前構築したRaspberry Piクラスターのネットワーク設定を変更して……

ryusa.hatenablog.com

作成したOperatorをデプロイしてみると……

f:id:RyuSA:20210703233015p:plain
Operatorで作成した"RASPBERRY" APに繋がった

手元のスマホからWi-Fiに接続&インターネットへアクセスできました!やったぜ🤪

ためしにhostapdがデプロイされている環境でRaspberry Piをインストールしてある部屋から部屋へ移動してみましたが、同じSSID/パスワードでうまくローミング(?)してシームレスに接続を継続してくれました。これはいい感じのモノが作れた……!

完走した感想

実はGo言語でがっつり開発したのは初挑戦だったので色々雑な作りになってしまいました。が、それでも今日も自宅で動いてくれています🥰毎日SSIDとパスワードを変えたい気持ちになりますねw

一方でKubebuilder側がまだM1 Mac(自分の開発環境)に正式対応しておらず、Operatorのテスト機構については全く手が出せていない状況です。ARM対応の実装を自分ですれば良いだけなのですが、なかなか食指が動かず……orz

定期的に開発できたらいいな〜と思ってます。

Kubernetesの仮想化ツールvclusterに触れてみる話

www.vcluster.com

vclusterはKubernetes上にKubernetesを起動して仮想的なKubernetesを楽しむことができるツールです。特定の名前空間上にk3sをデプロイしその中でリソースを作成、Syncerがリソースをホスト側のKubernetesへ同期をすることで実現しています。

結構楽しそうなツールだったので遊んでみたログを残しておきます。

# 環境
# Kubernetes Providor : Amazon EKS
❯ kubectl version --short
Client Version: v1.19.7
Server Version: v1.19.8-eks-96780e

Hello Worldしてみる

インストール (QuickStart)

専用のCLI(=vclusterCLI)、Helm、kubectlなどいくつかデプロイする方法はありますが、とりあえず動かしてみる分にはvclusterCLIをインストールして動かしてみると良いと思います。

www.vcluster.com

vclusterCLIのインストールが完了したら、あとはvclusterCLIを叩くだけでホスト側のKubernetesクラスター(=ホストクラスター)上に仮想的なKubernetes(=仮想クラスター)が動き始めます。

❯ vcluster create foo-cluster -n foo             
[info]   Creating namespace foo
[info]   execute command: helm upgrade foo-cluster vcluster --repo https://charts.loft.sh --kubeconfig /var/folders/qs/_40g0sgj1bz5lsc4pbbq7sn00000gn/T/689594003 --namespace foo --install --repository-config='' --values /var/folders/qs/_40g0sgj1bz5lsc4pbbq7sn00000gn/T/116523222
[done] √ Successfully created virtual cluster foo-cluster in namespace foo. Use 'vcluster connect foo-cluster --namespace foo' to access the virtual cluster

仮想クラスターから触ってみる

仮想クラスターへアクセスするためのkubeconfigを生成して実際にアクセスしてみます。CLIの記載通りにvcluster connect foo-cluster --namespace fooを叩くと仮想クラスターへの接続に必要なkubeconfigが生成されるので、kubeconfigを利用して接続してみます。

❯ vcluster connect foo-cluster --namespace foo
[done] √ Virtual cluster kube config written to: ./kubeconfig.yaml. You can access the cluster via `kubectl --kubeconfig ./kubeconfig.yaml get namespaces`
[info]   Starting port forwarding: kubectl port-forward --namespace foo foo-cluster-0 8443:8443
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
...
# 別のセッションを起動
❯ kubectl get all --kubeconfig ./kubeconfig.yaml 
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.229.214   <none>        443/TCP   3m49s

初期起動完了したので、ためしにいくつかリソースを仮想クラスター上に作成して動作確認してみます。

# Deploymentを作成
❯ kubectl create deployment --image nginx nginx --replicas 3 --kubeconfig ./kubeconfig.yaml 
deployment.apps/nginx created

# 名前空間を作成
❯ kubectl create namespace mginx --kubeconfig ./kubeconfig.yaml 
namespace/mginx created

# 作成した名前空間上にDeploymentを作成
❯ kubectl create deployment --image nginx nginx --replicas 3 --kubeconfig ./kubeconfig.yaml -n mginx 
deployment.apps/nginx created

# 作成したDeployment向けのServiceを作成する
❯ kubectl expose deployment -n mginx nginx --port 80 --kubeconfig ./kubeconfig.yaml
service/nginx exposed

# 作成したリソース一覧を確認する
❯ kubectl get all -A --kubeconfig ./kubeconfig.yaml                           
NAMESPACE     NAME                           READY   STATUS    RESTARTS   AGE
kube-system   pod/coredns-66c464876b-xrbxj   1/1     Running   0          18m
default       pod/nginx-6799fc88d8-tttl2     1/1     Running   0          5m51s
default       pod/nginx-6799fc88d8-hfs6z     1/1     Running   0          5m51s
default       pod/nginx-6799fc88d8-xt67s     1/1     Running   0          5m51s
mginx         pod/nginx-6799fc88d8-8skv7     1/1     Running   0          4m50s
mginx         pod/nginx-6799fc88d8-96q2w     1/1     Running   0          4m50s
mginx         pod/nginx-6799fc88d8-lzxlb     1/1     Running   0          4m50s

NAMESPACE     NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes   ClusterIP   10.100.229.214   <none>        443/TCP                  18m
kube-system   service/kube-dns     ClusterIP   10.100.253.26    <none>        53/UDP,53/TCP,9153/TCP   18m
mginx         service/nginx        ClusterIP   10.100.148.16    <none>        80/TCP                   7s

NAMESPACE     NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/coredns   1/1     1            1           18m
default       deployment.apps/nginx     3/3     3            3           5m51s
mginx         deployment.apps/nginx     3/3     3            3           4m50s

NAMESPACE     NAME                                 DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/coredns-66c464876b   1         1         1       18m
default       replicaset.apps/nginx-6799fc88d8     3         3         3       5m52s
mginx         replicaset.apps/nginx-6799fc88d8     3         3         3       4m51s

# 'alice'というServiceAccountを作成する
❯ kubectl create serviceaccount alice --kubeconfig ./kubeconfig.yaml 
serviceaccount/alice created

❯ kubectl get serviceaccounts --kubeconfig ./kubeconfig.yaml                                                     
NAME      SECRETS   AGE
default   1         19m
alice     1         5m51s

ざっと見た限り、普通のKubernetesのように動作しているように見えます。すごくいい感じ

ホストクラスターから眺めてみる

仮想クラスターで作成したリソースがホストクラスターからどのように見えるのかを確認してみます。

# すべてのリソースを取得する(関連する部分のみ抜粋)
❯ kubectl get all -A                                                          
NAMESPACE         NAME                                                       READY   STATUS    RESTARTS   AGE
foo               pod/coredns-66c464876b-xrbxj-x-kube-system-x-foo-cluster   1/1     Running   0          22m
foo               pod/foo-cluster-0                                          2/2     Running   0          23m
foo               pod/nginx-6799fc88d8-8skv7-x-mginx-x-foo-cluster           1/1     Running   0          9m17s
foo               pod/nginx-6799fc88d8-96q2w-x-mginx-x-foo-cluster           1/1     Running   0          9m17s
foo               pod/nginx-6799fc88d8-hfs6z-x-default-x-foo-cluster         1/1     Running   0          10m
foo               pod/nginx-6799fc88d8-lzxlb-x-mginx-x-foo-cluster           1/1     Running   0          9m17s
foo               pod/nginx-6799fc88d8-tttl2-x-default-x-foo-cluster         1/1     Running   0          10m
foo               pod/nginx-6799fc88d8-xt67s-x-default-x-foo-cluster         1/1     Running   0          10m
kube-system       pod/coredns-59847d77c8-8zpd4                               1/1     Running   0          5h26m
kube-system       pod/coredns-59847d77c8-vxqq5                               1/1     Running   0          5h26m
kube-system       pod/kube-proxy-9wh2q                                       1/1     Running   0          5h4m
kube-system       pod/kube-proxy-xqtb7                                       1/1     Running   0          5h4m

NAMESPACE       NAME                                           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
default         service/kubernetes                             ClusterIP   10.100.0.1       <none>        443/TCP                  5h27m
foo             service/foo-cluster                            ClusterIP   10.100.229.214   <none>        443/TCP                  23m
foo             service/foo-cluster-headless                   ClusterIP   None             <none>        443/TCP                  23m
foo             service/kube-dns-x-kube-system-x-foo-cluster   ClusterIP   10.100.253.26    <none>        53/UDP,53/TCP,9153/TCP   22m
foo             service/nginx-x-mginx-x-foo-cluster            ClusterIP   10.100.148.16    <none>        80/TCP                   4m34s
kube-system     service/kube-dns                               ClusterIP   10.100.0.10      <none>        53/UDP,53/TCP            5h27m

NAMESPACE    NAME                           READY   AGE
foo          statefulset.apps/foo-cluster   1/1     23m

# ServiceAccountを確認する
❯ kubectl get serviceaccounts | grep alice
// NO RESULT

仮想マシン上で作成したリソースの一部がホストクラスターから見えず(Deploymentとか)、一方仮想マシンで見えていたいくつのそれっぽいリソースがホストクラスターから見えています(Podとか)。 理由は単純で、仮想クラスター側で作成したリソースは仮想クラスター側のk3sで管理されています。そして一部のリソースを仮想クラスターからホストクラスターに同期し、そのあとのリソースの管理をホストクラスター側に委譲しています。

f:id:RyuSA:20210522220820p:plain

仮想クラスターが実際にどのリソースをホストクラスターへ委譲しているのかは把握してませんが、ざっと見た限り仮想クラスターからホストクラスターへPodやService、IngressやPVCなどを委譲しているようです。

少しだけ深堀り

別の仮想クラスターをHelmでインストールしてみます。vclusterはHelmコマンドを代替しているだけなので等価なHelmコマンドを叩くことでもvclusterをインストールすることが可能です。

❯ cat <<EOF > values.yaml
vcluster:
  image: rancher/k3s:v1.19.5-k3s2    
  extraArgs:
    # ホストクラスターのService CIDRをズラしてみる
    - --service-cidr=10.96.0.0/12
storage:
  size: 5Gi
EOF

❯ helm repo add loft https://charts.loft.sh
❯ helm repo update

# インストール
❯ helm upgrade --install bar loft/vcluster \
  --values values.yaml \
  --namespace bar \
  --create-namespace

# ホストクラスター側から見えるリソース
❯ kubectl get all -n bar
NAME        READY   STATUS    RESTARTS   AGE
pod/bar-0   2/2     Running   0          19s

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/bar            ClusterIP   10.100.50.206   <none>        443/TCP   19s
service/bar-headless   ClusterIP   None            <none>        443/TCP   19s

NAME                   READY   AGE
statefulset.apps/bar   1/1     19s

仮想クラスターコントロールプレーンのパラメータ調査

Helmでデプロイする際には細かいパラメータを指定することができます。仮想クラスターのコントロールプレーンであるk3sにデフォルトで指定されているパラメータを確認してみます。

❯ kubectl get pod -n bar bar-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
  name: bar-0
  namespace: bar
spec:
  containers:
  - args:
    - server
    - --write-kubeconfig=/k3s-config/kube-config.yaml
    - --data-dir=/data
    - --disable=traefik,servicelb,metrics-server,local-storage
    - --disable-network-policy
    - --disable-agent
    - --disable-scheduler
    - --disable-cloud-controller
    - --flannel-backend=none
    - --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle
    - --service-cidr=10.96.0.0/12
    command:
    - /bin/k3s
    image: rancher/k3s:v1.19.5-k3s2
    name: vcluster
    volumeMounts:
    - mountPath: /data
      name: data
    ...(snip)

rancher.com

k3sのパラメータを覗いて上記のパラメータと比較してみると

  • --disable=traefik,servicelb,metrics-server,local-storage
    • traefikやmetrics-serverを無効
  • --disable-network-policy
    • NetworkPolicy無効
  • --disable-agent
    • ローカルのエージェントを起動しない
  • --disable-scheduler
    • スケジューラーを無効(Podの配置がされない)
  • --disable-cloud-controller
    • Cloud Controllerを無効
  • --flannel-backend=none
    • Flannelを無効
  • --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle
    • いろいろ無効にしてる

k3sのもつController周りを無効にし、物理的な操作が必要になるリソースに関して何もしないようにしているように見えます。これで仮想クラスター内でDeploymentやStafulsetを扱い、PodやServiceをホストクラスターに委譲することができるわけですね。

仮想クラスターへの接続

実装を覗いてみるとvclusterCLIの各種コマンドはkubectlを利用して実現されています。それらを使ってvclusterCLI-lessで仮想クラスターに接続して見ます。

# kubeConfigを持ってきます
# https://github.com/loft-sh/vcluster/blob/v0.2.0/cmd/vclusterctl/cmd/connect.go#L93
❯ kubectl exec -n bar bar-0 -c syncer -- cat /root/.kube/config > barConfig.yaml

# ポートフォワードします(vcluster connectでの挙動を一緒)
# https://github.com/loft-sh/vcluster/blob/v0.2.0/cmd/vclusterctl/cmd/connect.go#L170
❯ kubectl port-forward -n bar bar-0 8443:8443
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
...

# 別のセッションを起動
❯ kubectl get all -A --kubeconfig ./barConfig.yaml
NAMESPACE     NAME                           READY   STATUS    RESTARTS   AGE
kube-system   pod/coredns-66c464876b-qrm6t   1/1     Running   0          102m

NAMESPACE     NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes   ClusterIP   10.100.50.206   <none>        443/TCP                  102m
kube-system   service/kube-dns     ClusterIP   10.100.38.116   <none>        53/UDP,53/TCP,9153/TCP   102m

NAMESPACE     NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/coredns   1/1     1            1           102m

NAMESPACE     NAME                                 DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/coredns-66c464876b   1         1         1       102m

おわりに

ざっくりとvclusterを触ってみましたが、名前空間ごとの環境分離がいい感じにできそうなツールに見えました。

さすがにこれを本番環境に導入、は今の段階では想定できませんが……例えば社内の勉強用/検証用の環境として用意したりする分には非常に便利だろうなと感じます。 仮想クラスターを作成して証明書情報を取得、LoadBalancerを立ててポートフォワード抜きで接続できるように設定すればお手軽Kubernetes as a Serviceの完成ですね。

おうちKubernetesをそれっぽく加工する話 - MetalLBとかExternalDNSとかcert-managerとか

おうちKubernetes(を含むベアメタルKubernetes)を「マネージドサービスっぽく」使えるようにしよう!というのが本記事の目標

一応前日譚

ryusa.hatenablog.com

ストレージのプロビジョニング

生のKubernetesで適当なPVCを作成しても何も反応しません。これは当然で、生のKubernetesにはストレージをどのように用意すれば良いのかが定義されていないからです。 プラグインなしでPVCとPVを紐つける場合、ユーザー自身がPVを作成してPVCと紐つける必要があります。このやり方は静的なプロビジョニングと呼ばれています。

例えば

apiVersion: v1
kind: PersistentVolume
metadata:
  name: static-pv
spec:
  storageClassName: manual 
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce 
  hostPath:
    path: "/tmp/static-pv"

というPVと

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: static-pvc
spec:
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi
  accessModes:
    - ReadWriteOnce

こんなPVCを作成することで、PVCstatic-pvcはPVstatic-pvを消費することができます。 さらに何かしらのPodがこのPVCを消費すると、PodからPVCへの書き込みがPVが定義されているノード上の/tmp/static-pvに書き込まれるようになります。

一応この運用をしていくことでアプリケーションが永続化ボリュームを利用することはできます。……が、こんなトイルをPVCを作成するたびにやっていてはキリがない!もっとクラウドサービスっぽくやりたい!

そこで、NFSを利用した動的プロビジョナー「nfs-subdir-external-provisioner」を利用してPVCから動的にPVを作成して消費する動的プロビジョニングのセットアップについて紹介します。

f:id:RyuSA:20210521200137p:plain

nfs-subdir-external-provisioner

github.com

nfs-subdir-external-provisionerは「すでに稼働している」NFSサーバーと接続し、PVCに合わせてNFSがストレージを用意してくれる動的プロビジョナーです。Helmでサクッとデプロイできるのも良いところです。 今回は下記のような環境で構築をします。

  • NFSサーバー
    • 192.168.100.101上で起動
    • /exports/ディレクトリを公開している

NOTE: 本音を言うと自宅にNFS搭載のNASが欲しかったのですが予算がないため購入を断念。代わりに家に転がっていたSSDUbuntuサーバーに追加でマウントしてNFSサーバーを構築しました。

インストール

インストールはHelmチャートが用意されているのでそれをそのまま利用するのが良いと思います。

# Helmリポジトリを追加
> helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

> helm repo update
# helm show values nfs-subdir-external-provisioner/nfs-subdir-external-provisioner で設定できる項目一覧を確認できます

> cat <<EOF > values.yaml
nfs:
  server: 192.168.100.101
  path: /exports/kubernetes/ # 専用のサブディレクトリを切っておきました(個人的な趣向)

storageClass:
  provisionerName: nfs
  defaultClass: true # デフォルトのStorageClassに設定
  name: nfs

resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 100m
    memory: 128Mi
EOF

# kube-system名前空間内にインストール
> helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -f values.yaml -n kube-system

動作確認

適当にPVCとそれを消費するPodを用意して動作確認をしてみます。

> cat <<EOF > dynamic-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  accessModes:
    - ReadWriteOnce
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - image: nginx
      volumeMounts:
        - mountPath: /data
          name: pvc
  restartPolicy: Always
  volumes:
    - name: pvc
      persistentVolumeClaim:
        claimName: dynamic-pvc
EOF

> kubectl apply -f dynamic-pvc-pod.yaml

> kubectl get pods nginx   
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          3m6s

実際にPodにPVCが消費されて、NFSディレクトリ配下にサブディレクトリが作成されていれば動作確認完了です。

ロードバランサーの作成

生のKubernetes上にデプロイされているPodの中に外からアクセスするには、kubectl port-forwardを利用するかtype: NodePortのServiceを利用するしか方法がありません。カッコ悪いですし、可用性にも欠けます。 もっとクールに、クラウドサービスのようなロードバランサーがほしいところです。

そこで、MetalLBとNGINX Ingress Controllerというツールを導入して、クラウドサービスのようなロードバランサーを自動で作成させてみましょう。

MetalLB

github.com

ベアメタルKubernetes定番のMetalLBです。細かい挙動に関しては解説しませんが、このMetalLBを利用することでtype: LoadBalancerなServiceを利用してKubernetesクラスタの外部から内部へアクセスすることができるようになります。

f:id:RyuSA:20210521213335p:plain

インストール

インストールするために、まず環境のネットワークで予約できるIPアドレス帯を確認しておいてください。今回は192.168.100.200から192.168.100.250までのIPアドレスが予約できていると仮定して話を進めます。

# MetalLBが利用するConfigmapを作成、MetalLBは起動時にこのConfigを読み込みます
> cat <<EOF > metallb-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 192.168.100.200-192.168.100.250 # 予約されたIPアドレスを指定する
EOF

# metallb-system名前空間を作成します(metallbが起動するデフォルトの名前空間)
> kubectl create namespace metallb-system

# リソースをデプロイします、今回は検証時最新だったMetalLB v0.9.6を利用しています
> kubectl apply -f metallb-config.yaml -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/metallb.yaml

動作確認

metallb-system名前空間上のPodがすべてRunningになったところで、適当なServiceを作成して動作確認をしましょう。

> cat <<EOF > lb.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb
spec:
  ports:
  - port: 80
  selector: {}
  type: LoadBalancer
EOF

> kubectl apply -f lb.yaml

> kubectl get svc lb
NAME   TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
lb     LoadBalancer   10.97.232.211   192.168.100.201  80:31932/TCP   11s

作成したServiceにEXTERNAL-IPが振られていれば、正常に動作しています。

NGINX Ingress Controller

L7ロードバランサーも欲しくなってきましたね?欲しくなりませんか??

github.com

NGINX Ingress ControllerはKubernetesIngressリソースを管理するIngressコントローラーです。個人的にIngressコントローラーの中で一番シンプルで使いやすいコントローラーだと思います。

NGINX Ingress Controllerはその名の通りNGINXを利用してIngressを実装しているControllerで、Ingressリソースを作成するとそのIngressの要求に合わせたNGINXが設定されます。

f:id:RyuSA:20210521214257p:plain

インストール

Helmでシンプルにインストールできます。

# Helmリポジトリを登録します
> helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

> helm repo update

# helm show values ingress-nginx/ingress-nginx で指定できるValueを確認できます
> helm install ingress-nginx ingress-nginx/ingress-nginx

動作確認

ingress-nginx-controllerのPodが起動した後に、Ingressリソースを作成して動作確認してみます。

> cat <<EOF > ingress-sample.yaml
# 適当なPod
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - image: nginx
     name: nginx
     ports:
     - containerPort: 80
        name: http
---
# Podに紐つけるService
apiVersion: v1
kind: Service
metadata:
  name: ingress-sample
spec:
  ports:
  - port: 80
     name: http
  selector:
    app: nginx
---
# Serviceに紐つけるIngress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample
spec:
  rules:
  - http:
      paths:
      - backend:
         service:
            name: ingress-sample
            port:
              number: 80
        path: /
        pathType: Prefix
EOF

# IngressとServiceとPodを作成
> kubectl apply -f ingress-sample.yaml

# Ingressリソースの状態を確認してみる
> kubectl get ingress                                                                   
NAME      CLASS    HOSTS   ADDRESS           PORTS   AGE
ingress   <none>   *       192.168.100.202   80      64s

# 実際にアクセスしてみる 
> curl 192.168.100.202                                                                  
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

しっかりIngressのAddressからPodへアクセスできることを確認して動作確認完了です。

ドメインTLS

ここまでで、おうちKubernetes(ベアメタル環境)においてもロードバランサーを経由してKubernetes外部から内部へアクセスできるようになりました。しかし、今のままではIPアドレスでアクセスせざるをえないためやっぱりカッコ悪いです。 またTLSを利用したセキュアな接続も実現できないため、安心安全なKubernetes生活にはまだ程遠いです。

そこで、External DNSとcert-managerを利用してServiceやIngressに対して自動的にドメインを発行&TLS化を行ってみます。

External DNS

github.com

External DNSは外部のDNSサーバーやDNSサービスに対してリクエストを行い、DNSレコードの変更を行ってくれるツールです。Kubernetesのリソースを監視して、リソースの状態に合わせて動的にDNSレコードの変更を行うこともできます。 自分でDNSサーバーを構築して利用する方法もありますが、後でTLS化を行うことも踏まえてPublicなDNSであるCloudflare DNSを利用することにします。

f:id:RyuSA:20210521220308p:plain

インストール

External DNSを利用する前に、ご自身のドメインを持っていることをご確認ください。ドメインをCloudflareに登録したのち、Cloudflare DNSAPIキーを取得してきます。

support.cloudflare.com

APIキーを元に、SecretリソースとExternal DNSをデプロイします。

# マニフェストを作成
# YOUR_DOMAIN/YOUR_API_KEY/YOUR_EMAIL を適切な値に置き換えてください
> cat <<EOF > externaldns.yaml
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-secret
  namespace: kube-system
type: Opaque
stringData:
  api-key: YOUR_API_KEY
  email: YOUR_EMAIL
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
  resources: ["ingresses"] 
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: external-dns
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.7.6
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=YOUR_DOMAIN
        - --provider=cloudflare
        env:
        - name: CF_API_KEY
          valueFrom:
            secretKeyRef:
              name: cloudflare-secret
              key: api-key
        - name: CF_API_EMAIL
          valueFrom:
            secretKeyRef:
              name: cloudflare-secret
              key: email
        resources: 
          limits:
            cpu: 50m
            memory: 50Mi
          requests:
            memory: 50Mi
            cpu: 10m
EOF

# External DNSをデプロイ
> kubectl apply -f externaldns.yaml

動作確認

デプロイしExternalDNSが起動した後、Ingressを作成してドメインが自動的に登録されていることを確認してみます。

> cat <<EOF > externaldns-sample.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    ports:
    - containerPort: 80
      name: http
---
apiVersion: v1
kind: Service
metadata:
  name: ingress
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
spec:
  rules:
  - host: nginx.YOUR_DOMAIN # 自身のドメインに置き換えてください
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
EOF

# Ingressたちをデプロイ
> kubectl apply -f externaldns-sample.yaml

デプロイ完了後、しばらくするとnginx.YOUR_DOMAINドメイン名に対してPrivateIPアドレスが登録されることを確認して動作確認完了です。(dig nginx.YOUR_DOMAINなど)

cert-manager

github.com

cert-managerはTLS証明書の発行やローテーションを自動化するためのツールで、Let's EncryptやHashicorp Vaultなどの認証局と連携することができます。cert-managerはCRDとしてデプロイされるIssuer(認証局)とCertificate(証明書)を元にTLS証明書を発行しSecretsに保存してくれます。

f:id:RyuSA:20210521223114p:plain

インストール

Helmでシンプルにインストールできます。

# Helmチャートを追加
> helm repo add jetstack https://charts.jetstack.io

> helm repo update

# cert-managerをインストール
> helm install cert-manager jetstack/cert-manager \
  --namespace kube-system \
  --set installCRDs=true # ビルドインのCRDをデプロイ

動作確認

今回は、CloudflareのDNSとLet's Encryptを利用して動作確認をします。先のセクションでExternal DNSをデプロイしたのでそれに合わせてリソースをデプロイします。

まずはIssuerを用意します。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
  namespace: kube-system
spec:
  acme:
    email: YOUR_EMAIL
    privateKeySecretRef:
      name: letsencrypt-staging-privatekey
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # server: https://acme-v02.api.letsencrypt.org/directory 本番環境はこちらのURL
    solvers:
    - dns01:
        cloudflare:
          apiKeySecretRef:
            name: cloudflare-secret
            key: api-key
          email: YOUR_EMAIL

ClusterIssuerはクラスタ全体で管理できるIssuerです。マルチテナントな環境では利用しにくいですが、今回はおうちKubernetesを対象としているのでClusterIssuerを利用しています。(Issuerも同じように定義できます) なお、spec.acme.privateKeySecretRef.nameで指定されたSecretにTLS証明書に利用する秘密鍵が保管されます。

次にCertificateをデプロイします。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: nginx-tls-staging
spec:
  dnsNames:
  - nginx.YOUR_DOMAIN
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: letsencrypt-staging
  secretName: nginx-tls-staging

Certificateをデプロイすると、cert-managerがIssuerの情報とともに実際にTLS証明書を取得してきてくれます。取得してきたTLS証明書はCertificateのspec.secretNameで指定したSecretに保存されます。

実際にIngressTLS証明書を指定してデプロイすると、エンドポイントのTLS化がされていることを確認できると思います。

> cat <<EOF > secure-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    ports:
    - containerPort: 80
      name: http
---
apiVersion: v1
kind: Service
metadata:
  name: secure-nginx
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-nginx
spec:
  rules:
  - host: nginx.YOUR_DOMAIN # 自身のドメインに置き換えてください
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
  tls:
  - hosts:
    - nginx.YOUR_DOMAIN
    secretName: nginx-tls-staging
EOF

# Ingressたちをデプロイ
> kubectl apply -f secure-nginx.yaml

# 証明書の発行を待ち、実際にアクセスしてみる
> curl https://nginx.YOUR_DOMAIN
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

おわりに

さっくりとおうちKubernetesをセットアップして、マネージドサービスっぽいKubernetesにお着替えさせてあげることができました。

一方で、Secretsの暗号化がされていなかったり監視機構が働いていなかったりと安心安全なKubernetes生活はまだ遠いです。この辺りも少しずつ改善していこうかなと思います。

おうちKubernetesを構築した話

f:id:RyuSA:20210516122353p:plain

Kubernetesの資格CKA/CKADを取得し、なにか自分にご褒美を与えたいな〜と思い……おうちKubernetesを構築することにしました!楽しみにしてたんだ!!

前日譚: ryusa.hatenablog.com

モチベーション 🦾

そもそもなんでおうちKubernetesなんて?と言う話から……

自分の仕事柄、職場のエンジニアの多くが自宅になにかしら機材を持ち込んで幸せになってる人が多いんですよね

先日も仕事の帰り道に数万する機材をポチって自宅に搬入したとか話を聞き、これはもはや一種の宗教じゃないのか とても羨ましい!ぜひ我が家にも!!と、ぼくも家に機材を搬入してみたいな〜と思ってました。 とはいえ、残念ながら自分は仕事で機材に触る機会も少なく、いきなり高価な機材に手を出すのは難易度が高すぎると感じていたので、まず簡単なKubernetesクラスターを家に置いておきたい欲求があったわけでした。

構築 - 物理編

まずは機材の調達から。 今回はRaspberry Pi 4 3台をクラスタリングするので備品を調達。

それらをPoEで繋ぐので適当なLANケーブルとハブを用意

SDカードは適当に家に転がっていた32GBのものを利用しています。

先にネタばらししてしまうと、Raspberry Piのみでクラスターを構築した際にどうしてもコントロールプレーンが不安定になってしまったため、家に転がっていたノートPCをコントロールプレーンとして活用しています。

コントロールプレーン:DELL Inspiron13 7378 (Ubuntu Server化済み)

構築 - 論理編

一通り機材のインストールが完了した後、いよいよKubernetesクラスターの構築作業です。microk8sで構築してもいいかな〜とも思いましたが、やはりここはおうちクラスター、妥協はしたくありません。kubeadmでインストールをしました。

今回のクラスターでは、下記の構成で構築していきました。

  • OS:Ununtu20.04
  • Kubernetes: v1.21.0
  • コンテナランタイム:containerd 1.4.4
  • CNI:Calico v3.18.3

またStorageのprovisionerとしてNFSを利用したいので、コントロールプレーンにはNFSもインストールしておきます。

最終的な我が家のネットワークはこちら

┌──────────────┐
│ The Internet │
└──────────────┘
       ▲
       │
       │
       │

 Router
 192.168.0.0/16

       │
       │         ┌───────────────────────┐
       │         │ Home Network          │
       ├─────────┤ 192.168.1.0/24 (DHCP) │
       │         └───────────────────────┘
       │
       │         ┌────────────────────────────────────────────┐
       │         │ Kubernetes Cluster Network                 │
       │         │ 192.168.100.0/24                           │
       │         │                                            │
       │         │                                            │
       │         │     ┌──────────────────────────────┐       │
       │         │     │ Seagull Ship(Control Plane)  │       │
       │         │     │ 192.168.100.101              │       │
       │         │     └──────────────────────────────┘       │
       │         │                                            │
       │         │     ┌──────────────────────────────┐       │
       │         │     │ Seagull (Data Plane)         │       │
       │         │     │ 192.168.100.[1-3]            │       │
       │         │     └──────────────────────────────┘       │
       └─────────┤                                            │
                 │     ┌──────────────────────────────┐       │
                 │     │ MetalLB                      │       │
                 │     │ 192.168.100.[200-250]        │       │
                 │     └──────────────────────────────┘       │
                 │                                            │
                 └────────────────────────────────────────────┘

これに合わせてuser-dataなどに色々インストールしていきます。

Control Plane

適当に転がっているUSBメモリUbuntuイメージを焼き、ブートさせました。公式のKubernetesのインストール手順に従ってKubernetesをインストールしました。

kubernetes.io

/etc/netplan/99-manual.yamlIPアドレス周りの設定を記入して完了(諸事情によりWifiでコントロールプレーンの運用をしてみました、今のところ安定して動いてます)

network:
  version: 2
  renderer: NetworkManager
  wifis:
    wlp1s0:
      dhcp4: no
      dhcp6: no
      addresses: [192.168.100.101/24]
      gateway4: 192.168.0.1
      nameservers:
        addresses:
        - 192.168.0.1
      access-points:
        "YOUR_SSID":
          password: "YOUR_PASSWORD"

Data Plane

Raspberry Piにインストールする予定のSDカードにuser-dataを埋め込み全自動kubernetesインストールまで組み込みました。下記のようなuser-dataを作成し、/system-boot/配下に配置しておくとRaspberry Pi起動時に自動的にkubeadm/kubeletのインストールがされるようになります。

#cloud-config

# hostname
hostname: seagull01

# Japan
timezone: "Asia/Tokyo"

# Never allow to ssh using password
ssh_pwauth: false
# User "user"
users:
  - name: user
    gecos: I am user
    primary_group: user
    groups: [adm, audio, cdrom, dialout, dip, floppy, lxd, netdev, plugdev, sudo, video]
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    lock_passwd: true
    ssh_import_id:
      - gh:RyuSA

# Update packages
package_update: true
package_upgrade: true
packages:
  - apt-transport-https
  - ca-certificates
  - curl
  - gnupg
  - lsb-release
  - nfs-common # for nfs client

mounts:
  - [ tmpfs, /tmp, tmpfs, "defaults,size=256m", "0", "0" ]
  - [ tmpfs, /var/tmp, tmpfs, "defaults,size=256m", "0", "0" ]

write_files:
  # iptablesがブリッジを通過するトラフィックを処理できるようにする 
  - content: |
      net.bridge.bridge-nf-call-ip6tables = 1
      net.bridge.bridge-nf-call-iptables = 1
    owner: root:root
    path: /etc/sysctl.d/k8s.conf
    permissions: '0644'
  # containerdに必要な設定
  - content: |
      overlay
      br_netfilter
    owner: root:root
    path: /etc/modules-load.d/containerd.conf
    permissions: '0644'
  # 必要なカーネルパラメータ
  - content: |
      net.bridge.bridge-nf-call-iptables  = 1
      net.ipv4.ip_forward                 = 1
      net.bridge.bridge-nf-call-ip6tables = 1
    owner: root:root
    path: /etc/sysctl.d/99-kubernetes-cri.conf
    permissions: '0644'

runcmd:
  - sudo swapoff -a
  - sudo sysctl --system
  # install containerd
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
  - echo "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
  - sudo apt-get update
  - sudo apt-get install containerd.io
  - sudo mkdir -p /etc/containerd
  - containerd config default | sed -e '/containerd.runtimes.runc.options/a \ \ \ \ \ \ \ \ \ \ \  SystemdCgroup = true' | sudo tee /etc/containerd/config.toml
  - sudo systemctl restart containerd
  # install kubeadm
  - sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
  - echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
  - sudo apt-get update
  - sudo apt-get install -y kubelet kubeadm kubectl
  - sudo apt-mark hold kubelet kubeadm kubectl containerd.io

起動して30分程度放置すると完了しているので、再起動して各ノードでkubeadm joinコマンドを叩いてKubernetesクラスターに参加すればOKです。かんたん!

f:id:RyuSA:20210516134402p:plain
今日も元気に動いてます

不安定なクラスタ

実は一度Raspberry Piのみでクラスターを構築してみました。……が、かなり不安定になってしまいすぐコントロールプレーンが死んでしまう事態に。 原因としては、どうやらSDカードのI/Oスループットが遅すぎるせいでetcdの書き込みが遅れてしまいkube-apiserverが死んでしまう……ということっぽかったです。(詳細は不明)

そのため、急遽家に転がっていたDellのノートPCをUbuntu Serverにしてコントロールプレーンとして再起動することでクラスターを安定化させました。

おわりに

さて、これでKubernetesクラスターが出来上がりましたが……知ってる人は知っての通り、このままでこのクラスターはあまり美味しくありません。

  • 永続化ボリュームが自動で作成されない
    • provisioner/storageclassがない
    • なんらかの方法でボリュームを作成して使いたい人に渡してあげる必要がある
  • Serviceのtype: LoadBalancerが使えない
    • Kubernetesの外へアプリケーションの公開が面倒になる
  • Ingressが使えない

かなり美味しくないです。美味しくないので、加工してより美味しくするための加工作業を進めていきます。(書いたら↓に追加していきます)

CKA(Certified Kubernetes Administrator)とCKAD(Certified Kubernetes Application Developer)の受験ログ

先月Linux Foundationがホストしている資格を取得しました🎉

f:id:RyuSA:20210516112854p:plainf:id:RyuSA:20210516112837p:plain

完走してきてすでに半月ほど経ってしまったので、忘れないうちにアウトプットしておこうと思い試験の概要や参考にした資料など公開しておきます。

メタ情報 👀

受験した際のメタ情報を書いておきます、人によって、受験時期によって違いますからね……

  • 自分はアプリケーションエンジニアです
    • Linuxのことなんもわからん……
  • Docker歴3年、Kubernetes歴10ヶ月
  • 勉強期間1ヶ月
  • 受験したのは4月中旬
  • Kubernetesのバージョンはv1.20

概要 📄

Kubernetesオープンソースのコンテナオーケストレーションツールで、CNCF(Cloud Native Computing Foundation)のGraduatedなプロジェクトです。 Kubernetesはコンテナ化されたアプリケーションやミドルウェアを管理し、複数台のサーバークラスター上にデプロイして水平スケールしたり自動復旧の機構を提供してくれます。

CKA

training.linuxfoundation.org

CKAはKubernetesの管理者として、基本的な操作からメンテナンス、アップグレードなど多様な知識を持っていることを証明する資格です。試験内容はペーパーテストではなく実技テスト、実際にWebコンソールを叩いて操作を行い点数がつけられます。CKAは後述するCKADと比べより広い知識が要求されます。そりゃインストールからメンテナンスと、要求されている内容が広いですからね……

具体的なシラバスGithubに公開されています。

github.com

受験当時のシラバスは下記の通りでした。

CKAD

training.linuxfoundation.org

CKADはKubernetesでアプリケーションの開発を行う開発者として、基本的な操作や監視、アプリケーションデザインの選択などより専門的な知識を持っていることを証明する資格です。CKAと同様、試験内容はペーパーテストではなく実技テストです。

具体的なシラバスGithubに公開されています。

github.com

受験当時のシラバスは下記の通りでした。

  • コア コンセプト13%
  • 構成18 %
  • マルチコンテナPod10%
  • マルチコンテナPodのデザイン パターン
  • 可観測性18%
  • Podのデザイン20%
  • サービスとネットワーキング13%
  • ステートの持続性8%

なおCKADは2021年にコンテンツが大幅に変わるらしく、すでに専用サイトでアナウンスがなされています。

training.linuxfoundation.org

より開発者らしく、OCI準拠のコンテナイメージの作成やCRD、Admission Controllなどの知識が増えるようですね。(難化する予感)

参考資料 👀

先人と同じく、勉強の際にいくつか資料を参考にしました。いずれも非常に優秀な資料で、CKA/CKADを受験目的としない人もKubernetesに携わる人には手を取って欲しいと思います。

Certified Kubernetes Administrator (CKA) with Practice Tests - Udemy

www.udemy.com

正直、CKA/CKAD共にこのUdemyクラスでほぼ十分でした。若干インド訛りが入っている英語での解説でしたが、字幕が用意されておりかつ丁寧に解説してあるので非常にわかりやすかったです。 やや古いKubernetes上での解説ですが、Kubernetesのコアコンセプトなどは現在最新のv.1.21でも通用する内容だと思います。

特に、このクラスには本番さながらのハンズオンがついています。実際にWebコンソールでコンテンツのPractice Testを受けることができるので自習する上で非常に助かりました。

Kubernetes完全ガイド 第2版 (Top Gear)

www.amazon.co.jp

日本語で書かれている、名前通りKubernetesの完全ガイドの本です。Kubernetesの基本的なコンセプトからアーキテクチャの話など幅広く解説してくださってます。「はじめに」の章で書かれていますが、この本一冊読み切るとCKA/CKADの取得に必要な知識はだいたい手に入ります。(インストールやメンテナンス周りは追加で勉強が必要そうですが……)

自分はKubernetes完全ガイドは辞書のようにして必要な時に字引きするといった使い方でした。もっと活用すればよかった……

個人的な体験談 👍

体験談はたくさんあるので箇条書きにて……

試験申し込みの話

  • 監督官の言語に注意
    • 監督官の言語選択であって、試験自体の言語とは関係なし(好きな言語で受験可能)
    • 英語でサクサクコミュニケーションが取れる人は通常のCKA/CKADで申し込みすればOK
    • 英語に難あり、という人はCKA-JP/CKAD-JPというセッションで申し込みすると監督官が日本語で対応してくれるらしいです(詳細は先人の体験談参照)
  • たまにセールやってます

"試験"そのものの話

  • 試験受ける場所は事前に決めておきましょう
    • 僕はCKA試験前日になってア○ホテル予約しました(個室で安定したワイヤードネットワークあり)
    • デイタイム利用で3k円、助かる……
  • ハンドブック(Linux Foundation Certification Exams: Candidate Handbook - T&C DOC)は読みましょう
    • PDFとして出力できるのでGoogle翻訳にぶち込んで日本語にしました
    • 「え、これダメなの!?」的なものもあるので要注意
  • 試験環境は事前チェックを通しておきましょう
    • 試験を予約するときに環境のpre-checkができます、5秒でチェック終わるのでやっておくことをおすすめします
    • 自分はM1 MacbookProで受験しましたが、マイク/カメラ共に組み込みのものを使用できました
    • マイク、カメラ、画面共有のChrome権限が必要になります

勉強とかの話

  • 基本的なLinuxコマンドは使えるようにしましょう
    • kubernetes.io に出てくる範囲のコマンドは使えるようにしておく
    • 一方、Linuxの基本的なコマンド(systemctlやjournalctl、bashのリダイレクトなど)も使えるようにしておく
    • 同僚にもらったLPICの教科書熟読した(試験直前はこれしかやらなかった)
  • kubectlを使いこなせるように練習をしておく
    • 例えば kubectl run --image nginx nginx --port 80 --env "FOO=BAR" --labels "env=prod" --command -- ls /etc とか
    • kubectl explain pod.spec --recursive | lessはCKAD試験で何度か使いました
  • 一番効果的な勉強は「Kubernetesを作って壊して直してみる」こと
    • kubeadmでクラスター作る際にパラメータを変えてみたりコンテナランタイムを変更してみたり
    • kube-proxyを弄るとどんな感じに壊れるかとか、kube-controller-managerを破壊するとどうなるのかとか
    • OSを強制シャットダウンするとどうなるかとか
  • 体験記を漁っておく
    • 試験自体のトラブルとか事前に知っておくとPanicになりにくくなります
    • CKAD試験開始と同時にWebコンソールにInternal Server Errorが表示されましたが、チャットにHelp Me!!!と訴えるとすぐ対応してくれました

試験中の話

  • 言語設定は選べます
    • 日本語/英語どちらでも受験できます
    • ただ日本語は機械翻訳感があり、意味が不明になっている部分もあるので困ったら英語に戻しましょう
    • UdemyのCKAクラスを受講していると英語の表現に慣れることができてGood
  • わからない問題は捨てる
    • 先人方の体験記にもある通り、わからん問題は捨てる
    • 捨てたところで数点失うだけ、満点を取ることが試験の目的ではないので……
  • トイレに行っておく
    • 試験中もトイレ退室は可能ですが、当然試験前に行っておきましょう
    • 一敗
  • Don't Panic

試験後の話

  • 試験終了後24時間以内にメールで結果が返ってきます
    • 自分は20時間くらいで返ってきました
  • 試験に受かると、credlyというサービスのバッチがもらえます
    • ↓のような感じ、SNSで自慢しよう!ww

www.credly.com

完走した感想 🏃‍♂️

正直なところ、CKAもCKADも1回目は落ちるかも?と思ってましたが一発で受かってほっとしてます。😎 試験自体もその勉強も、かなり楽しく終わらせることができて個人的にはかなり良い試験体験だったかな?と思ってます。

超個人的な感想なのですが……

  • CKA : 想像以上に簡単、コントローラーや証明書などの動きがわかってれば余裕
  • CKAD : 想像以上に時間が足りない、完答するにはガチタイピング能力が求められる
  • 共通 : kubectlが使いこなせればたぶん受かる

と、正直kubectlをしっかり使えれば受かるんじゃないかな?と思います。

ブログ開設 🎉

試しにQiitaから脱してはてなブログで情報のアウトプットをしていこうかなと思い開設しました

Who Am I

RyuSAと申します、某金融系事業に携わっている今年で4年目のエンジニアです。 お仕事ではフロントエンド/バックエンドのアプリケーションの設計や実装、最近はソフトウェアアーキテクトなどを担当してます。

JavaやSpring Bootでのバックエンド、TypeScriptとReactのフロントエンド……というありきたりな構成を担当しつつ、KubernetesなどのCloudNativeな技術スタックを利用したソフトウェアの開発などに興味持ってます

なんでブログ開設?

結論から言ってしまえば、一度ブログをメンテナンスしてみたかったからです、いや本当にしょうもない理由なんですが…… 今まではQiitaやZenn.devに色々アウトプットしてきたのですが、そろそろここらで試してみよ〜と思ったんですね

自分の個人感ですが、エンジニアとしての活動はアウトプットありき(俗に言う「アウトプット駆動」)だと思ってますので、ここらでプラットフォームを変えて気分一転してみるのもいいかなと

なんではてなブログ

実はnoteも開きました。 > note.com

エンジニア向けのブログサイトといえば、だいたい

  • はてなブログ
    • つよつよな人が多い気もする
  • note
    • キラキラしてる……
  • Medium
  • セルフホスト

が鉄板なのかなと思ってます(他にあれば教えて欲しい)

セルフホストする程トラフィック持ってないだろうし、Mediumは日本語フォントがちょっと苦手だし……という気持ちではてなブログとnoteの2つで試してみようと思い立った次第です。なのでしばらくははてなとnoteに同じ内容の記事が載ることになるかと思います。

どこかで統一しよう……

さいごに

ブログを書きながら、noteとはてなブログそれぞれの良いところの比較ができたらいいな〜と思ってます。今後もアウトプット駆動で学習を継続していきます。

現場からは以上です