hidemium's blog

日々学んだことをアウトプットする。

Rook Cephのダッシュボードのインストール

前回は、Rook Cephを使って分散ストレージのインストールを試してみました。今回は、Rookのダッシュボードのインストールを試してみました。

hidemium.hatenablog.com

構成

ダッシュボードのインストール

Ceph Clusterをデプロイする時に利用した、cluster.yamlの中でデフォルトでダッシュボードのインストールが有効になっているため、すでにインストールされている状態となります。

$ vi cluster.yaml
spec:
  dashboard:
    enabled: true

以下のように、 rook-ceph-mgr-dashboard サービスが起動していることを確認します。

$ kubectl -n rook-ceph get service
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
rook-ceph-mgr             ClusterIP   10.233.54.225   <none>        9283/TCP            2d15h
rook-ceph-mgr-dashboard   ClusterIP   10.233.37.83    <none>        8443/TCP            2d15h
rook-ceph-mon-a           ClusterIP   10.233.51.23    <none>        6789/TCP,3300/TCP   2d15h
rook-ceph-mon-b           ClusterIP   10.233.45.149   <none>        6789/TCP,3300/TCP   2d15h
rook-ceph-mon-c           ClusterIP   10.233.56.250   <none>        6789/TCP,3300/TCP   2d15h

前回までに、Ingressk8s_gatewayのインストールとサーバ証明書の作成を行っているため、ダッシュボード用にSecretとIngressの作成を行っていきます。

hidemium.hatenablog.com

Secretの作成

証明書は作成済みのため、Secretリソースに証明書を登録します。この時、Secretをrook-cephのnamespaceに入れておく必要があります。

$ export KEY_FILE="ingress-tls.key"
$ export CERT_FILE="ingress-tls.crt"
$ export CERT_NAME="ingress-tls"
$ kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} -n rook-ceph

Ingressの作成

ダッシュボード用のIngressマニフェストファイルが用意されているため、以下のように書き換えます。

$ vi dashboard-ingress-https.yaml
spec:
  tls:
    - hosts:
        - rook-ceph.kube.home.lab
      secretName: ingress-tls
  rules:
    - host: rook-ceph.kube.home.lab

ダッシュボード用のIngressを作成します。

$ kubectl create -f dashboard-ingress-https.yaml 

Ingressが作成できていることを確認します。

$ kubectl -n rook-ceph get ingress
NAME                      CLASS    HOSTS                     ADDRESS       PORTS     AGE
rook-ceph-mgr-dashboard   <none>   rook-ceph.kube.home.lab   10.0.50.201   80, 443   47

ダッシュボードへのログイン

ブラウザから https://rook-ceph.kube.home.lab に接続します。パスワードが求めらるので、ユーザ名は admin となり、パスワードについては、以下のコマンドでパスワードを確認します。

$ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo

ログインをすると以下のような画面が表示されます。

Cluster>Hostsからは、Ceph Clusterにあるworkerノードと、mon、mgr、osdがどのノードで起動しているかの状態が確認できます。

Cluster>OSDsからは、osdのステータスや、osdのデバイスの種類、ディスク容量が確認できます。

Block>imagesからは、前回作成したWordpress用のPVが確認できます。

発生した事象について

mgrが1台停止し、HEALTH_WARNのステータスとなることがありました。

bash-4.4$ ceph status
  cluster:
    id:     13a113cd-9060-4f1a-9fef-44d1bfe4fe26
    health: HEALTH_WARN
            1 mgr modules have recently crashed
 
  services:
    mon: 3 daemons, quorum a,b,c (age 2d)
    mgr: a(active, since 15m)
    osd: 4 osds: 4 up (since 2d), 4 in (since 2d)
 
  data:
    pools:   2 pools, 33 pgs
    objects: 99 objects, 201 MiB
    usage:   599 MiB used, 399 GiB / 400 GiB avail
    pgs:     33 active+clean

こちらのissueの状態と同じでした。

When I deply rook-ceph, the ceph returns "mgr modules have recently crashed" · Issue #11316 · rook/rook · GitHub

暫定対処として、mrgのmoduleの追加と、mgrのpodを再起動しました。

cluster.yamlに以下の設定を追加し、applyします。cephコマンドで、mgrのモジュールにrookが追加されたことを確認します。

$ vi cluster.yaml
spec:
  mgr:
    modules:
      - name: rook
        enabled: true
$ kubectl apply -f cluster.yaml
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
bash-4.4$ ceph mgr module ls
MODULE                              
balancer              on (always on)
crash                 on (always on)
devicehealth          on (always on)
orchestrator          on (always on)
pg_autoscaler         on (always on)
progress              on (always on)
rbd_support           on (always on)
status                on (always on)
telemetry             on (always on)
volumes               on (always on)
dashboard             on            
iostat                on            
nfs                   on            
prometheus            on            
restful               on            
rook                  on

モジュールの追加だけだと、healthのステータスが変わらなかったため、podの再起動を行います。

$ kubectl rollout restart deploy -n rook-ceph rook-ceph-mgr-b

ステータスを戻すために、cephのコマンドでcrash情報をアーカイブしました。

bash-4.4$ ceph crash ls 
ID                                                                ENTITY  NEW 
2023-01-21T22:35:55.341102Z_52cb8854-e243-4e2a-99c6-4ed8e66c2ac1  mgr.a    *   
bash-4.4$ ceph crash archive 2023-01-21T22:35:55.341102Z_52cb8854-e243-4e2a-99c6-4ed8e66c2ac1
bash-4.4$ ceph status
  cluster:
    id:     13a113cd-9060-4f1a-9fef-44d1bfe4fe26
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum a,b,c (age 2d)
    mgr: a(active, since 31m), standbys: b
    osd: 4 osds: 4 up (since 2d), 4 in (since 2d)
 
  data:
    pools:   2 pools, 33 pgs
    objects: 99 objects, 201 MiB
    usage:   599 MiB used, 399 GiB / 400 GiB avail
    pgs:     33 active+clean

参考

Rook Cephを使った分散ストレージのインストール

前回は、IngressSSL/TLSを有効にする方法を試してみました。今回は、Rook Cephを使って分散ストレージのインストールを試してみました。

hidemium.hatenablog.com

構成

PersistentVolumeについて

KubernetesのPersistentVolume(PV)を利用するために、ボリュームプラグインとしてKubernetesに組み込まれたIn-Tree Volume PluginとContainer Storage Interface(CSI)を利用することができます。詳細は以下のリストにありますが、ベンダー向けのIn-Tree Volume PluginからCSIに置き換わってきています。

Persistent Volumes | Kubernetes

homelabで利用する場合は、NFSを用意したり、vSphereのCSIのvSphere Container Storage Plug-inを利用するという方法が取れるかと思います。

VMware vSphere Container Storage Plug-in Documentation

今回の構成では、仮想マシンをworkerノードとしており、workerノードを物理サーバーと見たててハイパーコンバージドインフラストラクチャのような構成がためせないかと思い、分散ストレージのRook Cephを試してみました。

Rook Cephを利用する場合は、Ceph CSIを利用する形になります。

Rook Cephについて

Rook Cephは、Kubernetes上に分散ストレージのCephをデプロイし、ストレージの運用(展開、ブートストラップ、構成、プロビジョニング、スケーリング、アップグレード、移行、災害復旧、監視、およびリソース管理)を自動化してくれるストレージオペレーターです。

Rook

Cephは、オブジェクト、ブロック、ファイル ストレージといった複数のインターフェースでアクセスできる分散オブジェクトストレージプラットフォームです。

Welcome to Ceph — Ceph Documentation

Cephは4つのコンポーネントから成り立っています。

  1. Ceph Monitor (ceph-mon)
  2. Ceph Manager (ceph-rgw)
  3. Ceph OSD (object storage daemon (ceph-osd))
  4. Ceph Metadata Server (ceph-mds)

Ceph Monitorはクラスター状態を監視します。Monitorは少なくても3つ必要となります。

Ceph Managerはメトリクスやシステム負荷を監視やダッシュボードの提供します。Managerはすくなくても2つ必要となります。

Ceph OSDはデータを保存し、データのレプリケーションリカバリ、リバランスを処理し、Ceph Monitor と Ceph Manager に監視情報を提供し ます。OSDは少なくても3つ必要となります。

Ceph Metadata Serverは、CephFSのメタデータを保存し、Ceph Block Device とCephオブジェクトストレージは使用されません。

Kubernetesの再構築

Rook Cephを試すにあたり、以前構築した手順から少し変更を加えます。

hidemium.hatenablog.com

Rook Cephでは通常のデプロイをする場合は、最低3台のノードが必要なため、workerノードを2台から4台に増やします。Kubesprayのhosts.ymlを以下のように書き換えます。

$ vi hosts.yml
all:
  hosts:
    kube-master01:
      ansible_host: 10.0.50.30
      ip: 10.0.50.30
      access_ip: 10.0.50.30
    kube-master02:
      ansible_host: 10.0.50.31
      ip: 10.0.50.31
      access_ip: 10.0.50.31
    kube-master03:
      ansible_host: 10.0.50.32
      ip: 10.0.50.32
      access_ip: 10.0.50.32
    kube-worker01:
      ansible_host: 10.0.50.33
      ip: 10.0.50.33
      access_ip: 10.0.50.33
    kube-worker02:
      ansible_host: 10.0.50.34
      ip: 10.0.50.34
      access_ip: 10.0.50.34
    kube-worker03:
      ansible_host: 10.0.50.35
      ip: 10.0.50.35
      access_ip: 10.0.50.35
    kube-worker04:
      ansible_host: 10.0.50.36
      ip: 10.0.50.36
      access_ip: 10.0.50.36
  children:
    kube_control_plane:
      hosts:
        kube-master01:
        kube-master02:
        kube-master03:
    kube_node:
      hosts:
        kube-worker01:
        kube-worker02:
        kube-worker03:
        kube-worker04:
    etcd:
      hosts:
        kube-master01:
        kube-master02:
        kube-master03:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}

また、CNIをcalicoからflannelに変更します。

$ vi inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
- kube_network_plugin: calico
+ kube_network_plugin: flannel

masterノードとworkerノードのデプロイ

Ubuntu 22.04のテンプレートを作成し、以下のスペックでテンプレートを用意しておきます。

  • CPU: 1vCPU
  • Memory: 4GB
  • Disk: 100GB

masterノードとworkerノードのスペックはそれぞれの以下の通りになります。

node vCPU Memory Disk1 Disk2
master 1 4GB 100GB -
worker 1 4GB 100GB 100GB

govcを使って、テンプレートからのクローンやcloud-initの設定、追加ディスクのアタッチをまとめて、以下のように実行できます。

$ for VMNAME in kube-worker0{1..4}; do
  echo ${VMNAME}
  export VM="/Datacenter/vm/path/to/$VMNAME"
  govc vm.clone -vm template-vm -on=false -folder="/Datacenter/vm/k8s" $VMNAME
  export METADATA=$(gzip -c9 <metadata-${VMNAME}.yaml | { base64 -w0 2>/dev/null || base64; }) USERDATA=$(gzip -c9 <userdata-kube.yaml | { base64 -w0 2>/dev/null || base64; })
  govc vm.change -vm "${VM}" -e guestinfo.metadata="${METADATA}" -e guestinfo.metadata.encoding="gzip+base64" -e guestinfo.userdata="${USERDATA}" -e guestinfo.userdata.encoding="gzip+base64"
  govc vm.disk.create -vm ${VM} -name ${VMNAME}/${VMNAME}-disk2 -ds  datastore_name -size 100G
  govc vm.power -on "${VM}"
done

削除する場合は、以下のようにも実行できます。

$ for VMNAME in kube-worker0{1..3}; do
  echo ${VMNAME}
  export VM="/Datacenter/vm/path/to/$VMNAME"
  govc vm.info "${VM}"
  govc vm.destroy "${VM}"
done

Kubesprayによるクラスタ構築

以下のコマンドで再度Kubernetesクラスタを構築します。

$ ansible-playbook -i inventory/mycluster/hosts.yml  --become --become-user=root cluster.yml

workerノードの状態の確認

デプロイ直後のworkerノードの状態を確認しておきます。

Disk2として接続したディスクが、 sdb として表示されていることが分かります。 sda 側はOS領域としてLVMで構成されています。

$ lsblk -f
NAME                      FSTYPE      FSVER    LABEL UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
loop0                     squashfs    4.0                                                         0   100% /snap/lxd/23541
loop2                     squashfs    4.0                                                         0   100% /snap/core20/1738
loop3                     squashfs    4.0                                                         0   100% /snap/snapd/17883
loop4                     squashfs    4.0                                                         0   100% /snap/lxd/22923
loop5                     squashfs    4.0                                                         0   100% /snap/snapd/17950
loop6                     squashfs    4.0                                                         0   100% /snap/core20/1778
sda                                                                                                        
├─sda1                                                                                                     
├─sda2                    ext4        1.0            a2ece314-f49d-4c09-ad59-6f90402eb8e6      1.3G    14% /boot
└─sda3                    LVM2_member LVM2 001       nv6J0B-VyPL-K1x1-Rfti-oHC0-94RQ-DQ3tex                
  └─ubuntu--vg-ubuntu--lv ext4        1.0            2bcaf132-16b8-4b64-9e35-f200b40dfbf9       81G    12% /
sdb

Rookのインストール

それではRookのインストールを行っています。

まずは、rookのリポジトリをダウンロードします。

$ git clone --single-branch --branch v1.10.9 https://github.com/rook/rook.git

Rook Operatorをデプロイします。

$ cd rook
$ cd deploy/examples
$ kubectl create -f crds.yaml -f common.yaml -f operator.yaml

rook-ceph-operatorのpodが起動してきたことを確認します。

$ kubectl get pods -n rook-ceph
NAME                                  READY   STATUS    RESTARTS   AGE
rook-ceph-operator-76686d66c9-tjh8c   1/1     Running   0          6m13s

次にCeph Clusterをデプロイします。

$ kubectl create -f cluster.yaml

以下のようにpodが起動してくることを確認します。

$ kubectl -n rook-ceph get pod -o wide
NAME                                                      READY   STATUS      RESTARTS   AGE     IP             NODE            NOMINATED NODE   READINESS GATES
csi-cephfsplugin-688zw                                    2/2     Running     0          46h     10.0.50.35     kube-worker03   <none>           <none>
csi-cephfsplugin-8qtzx                                    2/2     Running     0          46h     10.0.50.36     kube-worker04   <none>           <none>
csi-cephfsplugin-bkdqh                                    2/2     Running     0          46h     10.0.50.34     kube-worker02   <none>           <none>
csi-cephfsplugin-njz7s                                    2/2     Running     0          46h     10.0.50.33     kube-worker01   <none>           <none>
csi-cephfsplugin-provisioner-75b9f74d7b-9spfw             5/5     Running     0          46h     10.233.68.4    kube-worker02   <none>           <none>
csi-cephfsplugin-provisioner-75b9f74d7b-bvhjd             5/5     Running     0          46h     10.233.70.5    kube-worker03   <none>           <none>
csi-rbdplugin-4h4lk                                       2/2     Running     0          46h     10.0.50.33     kube-worker01   <none>           <none>
csi-rbdplugin-bglgs                                       2/2     Running     0          46h     10.0.50.35     kube-worker03   <none>           <none>
csi-rbdplugin-p9xgj                                       2/2     Running     0          46h     10.0.50.34     kube-worker02   <none>           <none>
csi-rbdplugin-provisioner-66d48ddf89-b6845                5/5     Running     0          46h     10.233.67.5    kube-worker04   <none>           <none>
csi-rbdplugin-provisioner-66d48ddf89-rpjws                5/5     Running     0          46h     10.233.69.3    kube-worker01   <none>           <none>
csi-rbdplugin-pspw6                                       2/2     Running     0          46h     10.0.50.36     kube-worker04   <none>           <none>
rook-ceph-crashcollector-kube-worker01-9f7b6f546-q5h9b    1/1     Running     0          46h     10.233.69.6    kube-worker01   <none>           <none>
rook-ceph-crashcollector-kube-worker02-67f84cb9b9-qfgdz   1/1     Running     0          46h     10.233.68.9    kube-worker02   <none>           <none>
rook-ceph-crashcollector-kube-worker03-7687b79c6d-srxnr   1/1     Running     0          46h     10.233.70.6    kube-worker03   <none>           <none>
rook-ceph-crashcollector-kube-worker04-76c99494c5-6psss   1/1     Running     0          46h     10.233.67.11   kube-worker04   <none>           <none>
rook-ceph-mgr-a-6d8bb6dc9b-4hnkh                          3/3     Running     0          46h     10.233.67.7    kube-worker04   <none>           <none>
rook-ceph-mgr-b-96b47c755-bv2h8                           3/3     Running     0          46h     10.233.68.6    kube-worker02   <none>           <none>
rook-ceph-mon-a-bb685559c-gtjwm                           2/2     Running     0          46h     10.233.70.4    kube-worker03   <none>           <none>
rook-ceph-mon-b-6dcb8f97b9-xgnvn                          2/2     Running     0          46h     10.233.67.6    kube-worker04   <none>           <none>
rook-ceph-mon-c-c97b95cf6-l7ng4                           2/2     Running     0          46h     10.233.68.5    kube-worker02   <none>           <none>
rook-ceph-operator-54fc78d7c9-6n7gd                       1/1     Running     0          46h     10.233.67.2    kube-worker04   <none>           <none>
rook-ceph-osd-0-59645d69db-p7x56                          2/2     Running     0          46h     10.233.70.8    kube-worker03   <none>           <none>
rook-ceph-osd-1-69c67b85f9-cbqfc                          2/2     Running     0          46h     10.233.68.10   kube-worker02   <none>           <none>
rook-ceph-osd-2-7b56666677-qwt2x                          2/2     Running     0          46h     10.233.67.10   kube-worker04   <none>           <none>
rook-ceph-osd-3-5bf94d4467-pznrt                          2/2     Running     0          46h     10.233.69.5    kube-worker01   <none>           <none>
rook-ceph-osd-prepare-kube-worker01-l7pkg                 0/1     Completed   0          3h51m   10.233.69.13   kube-worker01   <none>           <none>
rook-ceph-osd-prepare-kube-worker02-dxx2c                 0/1     Completed   0          3h51m   10.233.68.24   kube-worker02   <none>           <none>
rook-ceph-osd-prepare-kube-worker03-2779b                 0/1     Completed   0          3h51m   10.233.70.18   kube-worker03   <none>           <none>
rook-ceph-osd-prepare-kube-worker04-vdd5w                 0/1     Completed   0          3h51m   10.233.67.23   kube-worker04   <none>           <none>

Ceph Clusterの状態を確認するため、以下のコマンドで確認します。MESSAGEが、Cluster created successfully となっており、HEALTHが HEALTH_OK となっていれば問題ありません。

$ kubectl -n rook-ceph get cephcluster
NAME        DATADIRHOSTPATH   MONCOUNT   AGE   PHASE   MESSAGE                        HEALTH        EXTERNAL
rook-ceph   /var/lib/rook     3          39h   Ready   Cluster created successfully   HEALTH_OK

toolboxを使って、Ceph Clusterの状態を確認しておきます。healthが HEALTH_OK となっていることが分かります。また、monのデーモンが3つ起動し、mgrが2つあり、osdが4つ起動していることが分かります。

$ kubectl apply -f toolbox.yaml
$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
bash-4.4$ ceph status
  cluster:
    id:     13a113cd-9060-4f1a-9fef-44d1bfe4fe26
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum a,b,c (age 13h)
    mgr: b(active, since 13h), standbys: a
    osd: 4 osds: 4 up (since 13h), 4 in (since 13h)
 
  data:
    pools:   1 pools, 1 pgs
    objects: 2 objects, 449 KiB
    usage:   45 MiB used, 400 GiB / 400 GiB avail
    pgs:     1 active+clea

Rookのインストールの補足

特定ノードや特定デバイスにインストールをしたい場合に、cluster.yamlで以下のパラメータの設定を行います。

今回は、masterノードとworkerノードはKubesprayでデプロイする際に指定を行っており、workerノードはsdbがRaw Deviceとなっており、すべて同じ構成となっているためデフォルト値のまま利用しています。デフォルト値のままで、workerノードのsdbだけが利用されるようになります。

$ vi cluster.yaml
spec:
  storage: 
    useAllNodes: true
    useAllDevices: true
    # nodes:
    #   - name: "172.17.4.201"
    #     devices: # specific devices to use for storage can be specified for each node
    #       - name: "sdb"

Cephストレージクラスタを構成する場合は、システム要件として以下の少なくても一つの構成が必要となっています。

* Raw devices (no partitions or formatted filesystems)
* Raw partitions (no formatted filesystem)
* LVM Logical Volumes (no formatted filesystem)
* Persistent Volumes available from a storage class in block mode

仮想マシンにハード ディスク 2を接続し、OS内で特に設定はせずに、OSでデバイスが認識できていればシステム要件は満たせそうでした。

workerノードの状態の確認

Rookをインストール後のworkerノードの状態を確認しておきます。

Disk2として接続した sdb がceph_bluestoreとしてフォーマットされていることが分かります。Raw deviceの場合、自動的にceph_bluestoreが選択されるようです。

$ lsblk -f
NAME                FSTYPE         FSVER    LABEL UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
loop0               squashfs       4.0                                                         0   100% /snap/lxd/23541
loop1               squashfs       4.0                                                         0   100% /snap/core20/1778
loop2               squashfs       4.0                                                         0   100% /snap/snapd/17883
loop3               squashfs       4.0                                                         0   100% /snap/core20/1738
loop4               squashfs       4.0                                                         0   100% /snap/lxd/22923
loop5               squashfs       4.0                                                         0   100% /snap/snapd/17950
sda                                                                                                     
├─sda1                                                                                                  
├─sda2              ext4           1.0            a2ece314-f49d-4c09-ad59-6f90402eb8e6      1.3G    14% /boot
└─sda3              LVM2_member    LVM2 001       nv6J0B-VyPL-K1x1-Rfti-oHC0-94RQ-DQ3tex                
  └─ubuntu--vg-ubuntu--lv
                    ext4           1.0            2bcaf132-16b8-4b64-9e35-f200b40dfbf9     80.6G    12% /
sdb                 ceph_bluestore

StorageClassのデプロイ

ブロックストレージ(RDB)のStorageClassとCephBlockPoolをデプロイします。

$ cd deploy/examples
$ kubectl create -f csi/rbd/storageclass.yaml

Wordpressによる動作確認

Wordpress をデプロイし、動作確認を行います。

$ kubectl create -f mysql.yaml
$ kubectl create -f wordpress.yaml

WordpressWordpress用のMySQLのpodが起動していることを確認します。

$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
wordpress-7cf5c5c8b-lbjln          1/1     Running   0          2m9s
wordpress-mysql-6f99c59595-djvm9   1/1     Running   0          2m15s

Wordpressのサービスを確認すると、 type: LoadBalancer でMetalLBからIPアドレスが付与されていることが分かります。

$ kubectl get svc wordpress
NAME        TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
wordpress   LoadBalancer   10.233.38.114   10.0.50.203   80:30242/TCP   19h

ブラウザから http://10.0.50.203/ にアクセスし、Wordpressの初期設定が画面が表示されることを確認します。

PVとPVCが作成されていることを確認します。

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS      REASON   AGE
pvc-32b6f27b-5d03-4ed8-9478-82cf64ed73f1   20Gi       RWO            Delete           Bound    default/mysql-pv-claim   rook-ceph-block            19h
pvc-bcfab821-a7f8-4df0-8788-0144e06f2435   20Gi       RWO            Delete           Bound    default/wp-pv-claim      rook-ceph-block            19h
$ kubectl get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-pv-claim   Bound    pvc-32b6f27b-5d03-4ed8-9478-82cf64ed73f1   20Gi       RWO            rook-ceph-block   19h
wp-pv-claim      Bound    pvc-bcfab821-a7f8-4df0-8788-0144e06f2435   20Gi       RWO            rook-ceph-block   19h

toolboxからceph statusを確認し、objectsやusageが増えていることを確認します。

bash-4.4$ ceph status
  cluster:
    id:     13a113cd-9060-4f1a-9fef-44d1bfe4fe26
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum a,b,c (age 14h)
    mgr: b(active, since 14h), standbys: a
    osd: 4 osds: 4 up (since 14h), 4 in (since 14h)
 
  data:
    pools:   2 pools, 33 pgs
    objects: 118 objects, 275 MiB
    usage:   816 MiB used, 399 GiB / 400 GiB avail
    pgs:     33 active+clean
 
  io:
    client:   2.0 KiB/s wr, 0 op/s rd, 0 op/s wr

リソースの削除

動作確認ができたため、リソースを削除しておきます。

$ kubectl delete -f wordpress.yaml
$ kubectl delete -f mysql.yaml

発生した事象について

上記の記載を見ると、特に通常の手順で簡単にインストールできそうに見えますが、いくつかはまりました。

KubesprayのデフォルトのCNIはcalicoですが、この状態でRookをインストールすると、Configuring Ceph Monsのメッセージのまま、monの起動でとまってしまい、インストールが正常に完了しませんでした。

$ kubectl -n rook-ceph get cephcluster
NAME        DATADIRHOSTPATH   MONCOUNT   AGE     PHASE         MESSAGE                 HEALTH   EXTERNAL
rook-ceph   /var/lib/rook     3          2m54s   Progressing   Configuring Ceph Mons

rook-ceph-operatorのログには、op-mon: mons running: [a]が表示され続ける状態となりました。

$ kubectl -n rook-ceph logs -l app=rook-ceph-operator -f
2023-01-18 21:40:56.423974 I | op-mon: waiting for mon quorum with [a]
2023-01-18 21:40:56.703448 I | op-mon: mons running: [a]
2023-01-18 21:41:17.104072 I | op-mon: mons running: [a]
2023-01-18 21:41:37.314370 I | op-mon: mons running: [a]
2023-01-18 21:41:57.519969 I | op-mon: mons running: [a]
2023-01-18 21:42:17.716778 I | op-mon: mons running: [a]
2023-01-18 21:42:37.944062 I | op-mon: mons running: [a]
2023-01-18 21:42:58.128773 I | op-mon: mons running: [a]
2023-01-18 21:43:18.339845 I | op-mon: mons running: [a]
2023-01-18 21:43:38.572259 I | op-mon: mons running: [a]

ログの出力として、他に異常はなく、何が起きているか確認できない状態でしたが、monの数を1に変更した場合に処理が通過することがあり、rook-ceph-operatorとmonのpod間の通信に問題がありそうということが見えていました。

また、以下のissueにも同様の事象が報告されており、calicoからflannelに変更することで事象が改善したとありました。

Mons not forming quorum · Issue #7769 · rook/rook · GitHub

こちらのissueでも議論されており、Rookとcalicoの組み合わせといったKubernetesのツール間での組み合わせ問題がありそうです。

Rook Ceph Mons unable to form quorum when using Calico CNI · Issue #5065 · rook/rook · GitHub

参考

IngressでSSL/TLSを有効にする

前回は、CoreDNSのk8s_gatewayプラグインを使って、DNSサーバーへの連携について試してみました。その際に、ブラウザで信頼されない証明書の警告が表示されました。今回は、IngressSSL/TLSを有効にする方法を試してみました。

hidemium.hatenablog.com

構成

秘密鍵サーバー証明書の作成

homelabで利用する目的のため、認証局は作成せずに、CSRなしで、秘密鍵から自己署名のサーバー証明書を作成しています。

以下のように秘密鍵サーバー証明書を作成します。Chrome58以降は、証明書のCommonNameフィールドを評価せず、「X509v3 extensions: X509v3 Subject Alternative Name」を評価するようになったため、subjectAltNameを指定しておきます。

$ export KEY_FILE="nginx-tls.key"
$ export CERT_FILE="nginx-tls.crt"
$ export HOST="nginx.kube.home.lab"
$ export CERT_NAME="nginx-tls"
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST}/O=${HOST}" -addext "subjectAltName = DNS:${HOST}"

証明書の登録

Secretリソースに証明書を登録します。

$ kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE}

動作確認

nginxサーバーをデプロイし、 nginx.kube.home.lab というホスト名と先ほど作成したSecretリソースを指定しIngressを作成します。

kubectl create deployment nginx --image=nginx --port=80
kubectl expose deployment nginx
kubectl create ingress nginx --class=nginx --rule ${HOST}/=nginx:80,tls=${CERT_NAME}

curlでアクセスし、証明書が利用されていることを確認します。

$ curl -k -v https://nginx.kube.home.lab
:
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=nginx.kube.home.lab; O=nginx.kube.home.lab
:

ブラウザから https://nginx.kube.home.lab にアクセスし、ブラウザで信頼されない証明書の警告が表示されます。Chromeの場合は、ブラウザからサーバー証明書をダウンロードし、Chromeの設定のデバイス証明書の管理から、インポートをクリックし、証明書をすべて次のストアに配置するで信頼されたルート証明機関を選択し、サーバー証明書を登録します。

ブラウザを再起動するか、シークレットモードで再度 https://nginx.kube.home.lab にアクセスし、ブラウザで信頼されない証明書の警告が表示されないことを確認します。

ワイルドカード証明書の作成

ワイルドカード証明書でも動作するか確認してみます。

以下のように秘密鍵サーバー証明書を作成します。

$ export KEY_FILE="ingress-tls.key"
$ export CERT_FILE="ingress-tls.crt"
$ export HOST="*.kube.home.lab"
$ export CERT_NAME="ingress-tls"
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST}/O=${HOST}" -addext "subjectAltName = DNS:${HOST}"

Secretリソースに証明書を登録します。

$ kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE}

nginxサーバーをデプロイし、 nginx.kube.home.lab というホスト名と先ほど作成したSecretリソースを指定しIngressを作成します。

kubectl create deployment nginx --image=nginx --port=80
kubectl expose deployment nginx
kubectl create ingress nginx --class=nginx --rule nginx.kube.home.lab/=nginx:80,tls=${CERT_NAME}

curlでアクセスし、証明書が利用されていることを確認します。

$ curl -k -v https://nginx.kube.home.lab
:
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.kube.home.lab; O=*.kube.home.lab
:

上記と同様に、ブラウザからサーバー証明書をダウンロードし、Chromeの設定のデバイス証明書の管理から、インポートをクリックし、証明書をすべて次のストアに配置するで信頼されたルート証明機関を選択し、サーバー証明書を登録します。

ブラウザを再起動するか、シークレットモードで再度 https://nginx.kube.home.lab にアクセスし、ブラウザで信頼されない証明書の警告が表示されないことを確認します。

nginxサーバー以外のサーバーをデプロイし、サブドメインでブラウザで信頼されない証明書の警告が表示されないか確認してみます。

httpdサーバーをデプロイし、 httpd.kube.home.lab というホスト名と先ほど作成したSecretリソースを指定しIngressを作成します。

kubectl create deployment httpd --image=httpd --port=80
kubectl expose deployment httpd
kubectl create ingress httpd --class=nginx --rule httpd.kube.home.lab/=httpd:80,tls=${CERT_NAME}

ブラウザから https://httpd.kube.home.lab にアクセスし、ブラウザで信頼されない証明書の警告が表示されないことを確認します。Ingressを作成する際に、ブラウザに証明書を登録せずに警告なしでアクセスすることができるようになりました。

おわりに

一度証明書を指定してIngressを作成すると、Ingressを削除後に再度Ingressを作成する際に、明示的に証明書を指定しなくてもSSL/TLSが有効になるように見えていました。ホスト名から証明書が自動選択されているように見えています。

また、一度ワイルドカード証明書Ingressを作成すると、明示的に証明書を指定せずにIngressを作成すると、ワイルドカード証明書が利用されているように見えていました。Secretリソースからワイルドカード証明書を削除しても、Ingressワイルドカード証明書が利用されているように見えており、おそらくPod内に証明書が残存してしまうのではと考えています。

そのため、Ingressで証明書を指定する場合は明示的に行ったほうがよさそうに見えています。

参考

CoreDNSのk8s_gatewayプラグインを使ったDNSサーバーへの連携

前回は、ingress-nginxを使ったIngressコントローラーのインストールを試してみました。今回は、Ingressで作成したDNSレコードをKubernetesクラスタ外からでも名前解決できるように、CoreDNSのk8s_gatewayプラグインを使って、DNSサーバーへの連携について試してみました。

hidemium.hatenablog.com

構成

はじめに

前回、Ingressを作成し、ホスト名でアクセスできることを確認しました。利用端末で/etc/hostsに以下のように追記をすれば利用はできますが、都度hostsファイルを編集するのは大変なため、DNSサーバーへDNSレコードを連携したくなります。

Ingressで作成したDNSレコードをKubernetesクラスタ外からでも名前解決できるようにするためには、一般的にはExternalDNSを利用されるようです。ExternalDNSは、ServiceやIngressなどのKubernetesのリソースを検知して、DNSプロバイダーへDNSレコードを連携してくれます。

GitHub - kubernetes-sigs/external-dns: Configure external DNS servers (AWS Route53, Google CloudDNS and others) for Kubernetes Ingresses and Services

ExternalDNSがサポートするDNSプロバイダーとして、クラウドのサービスやCoreDNSなどが利用できることが分かります。

CoreDNSはGoで記述されており、Kubernetesクラスタの一部で稼働しています。CoreDNSをデプロイする場合、ExternalDNSと連携するために、etcdを利用する方法もありますが、CoreDNSのhelmチャートで利用されているetcd-operatorがアーカイブされており、利用できないことが分かります。

CoreDNSとetcdのマニフェストファイルを書くか、docker-composeを使って構築し、ExternalDNSと連携することもできますが、労力がかかりそうなため、ExternalDNSは使わずにCoreDNSのk8s_gatewayプラグインを利用しました。

k8s_gatewayプラグインは、内部のkube-dnsとは別のインスタンスとしてデプロイでき、Kubernetesクラスタの外部DNSインターフェースとして機能します。

k8s_gatewayは、nginxinc kubernetes-ingressをサポートしていますが、今回は、ingress-nginxで動作確認をしています。

k8s_gatewayのインストール

k8s_gatewayのインストールは、helmチャートを提供してくれているため、以下のようにhelmをインストールします。

$ curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
$ sudo apt-get install apt-transport-https --yes
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
$ sudo apt-get update
$ sudo apt-get install helm

Helm | Helm のインストール

以下のように、k8s_gatewayをインストールします。

$ helm repo add k8s_gateway https://ori-edge.github.io/k8s_gateway/
$ helm install exdns --set domain=kube.home.lab k8s_gateway/k8s-gateway

便利なことに、k8s_gatewayがLoadBalancerで公開され、EXTERNAL-IPにMetalLBで設定したIPアドレスが付与されていることが分かります。

$ kubectl get pod,svc,ingress
NAME                                     READY   STATUS    RESTARTS   AGE
pod/exdns-k8s-gateway-58bf7c6c75-qksjm   0/1     Running   0          7s

NAME                        TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/exdns-k8s-gateway   LoadBalancer   10.233.13.50   10.0.50.202   53:30273/UDP   7s
service/kubernetes          ClusterIP      10.233.0.1     <none>        443/TCP        14d

k8s_gatewayの動作確認

nginxサーバーをデプロイし、 nginx.kube.home.lab というホスト名でIngressを作成し、ホスト名でアクセスできることを確認します。

$ kubectl create deployment nginx --image=nginx --port=80
$ kubectl expose deployment nginx
$ kubectl create ingress nginx --class=nginx --rule nginx.kube.home.lab/=nginx:80
$ kubectl get pod,svc,ingress
NAME                                     READY   STATUS    RESTARTS   AGE
pod/exdns-k8s-gateway-58bf7c6c75-qksjm   1/1     Running   0          14m
pod/nginx-ff6774dc6-kx9mr                1/1     Running   0          15s

NAME                        TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/exdns-k8s-gateway   LoadBalancer   10.233.13.50   10.0.50.202   53:30273/UDP   14m
service/kubernetes          ClusterIP      10.233.0.1     <none>        443/TCP        14d
service/nginx               ClusterIP      10.233.50.84   <none>        80/TCP         11s

NAME                              CLASS   HOSTS                 ADDRESS   PORTS   AGE
ingress.networking.k8s.io/nginx   nginx   nginx.kube.home.lab             80      5s

nginxサーバーに対してホスト名でアクセスできるか確認します。

$ INGRESS_EXTERNAL_IP=`kubectl get svc --namespace=ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
$ echo $INGRESS_EXTERNAL_IP
10.0.50.201
$ curl $INGRESS_EXTERNAL_IP -H "Host: nginx.kube.home.lab"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

digでk8s_gatewayのEXTERNAL-IPに対して、名前解決ができるか確認します。

$ dig @10.0.50.202 nginx.kube.home.lab
; <<>> DiG 9.16.1-Ubuntu <<>> @10.0.50.202 nginx.kube.home.lab
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5905
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 1e039be809cc823c (echoed)
;; QUESTION SECTION:
;nginx.kube.home.lab.           IN      A

;; ANSWER SECTION:
nginx.kube.home.lab.    300     IN      A       10.0.50.201

;; Query time: 4 msec
;; SERVER: 10.0.50.202#53(10.0.50.202)
;; WHEN: Sun Jan 08 12:16:48 JST 2023
;; MSG SIZE  rcvd: 95

EdgeRouterの設定

EdgeRouterを利用端末のDNSサーバーとして利用しています。EdgeRouterは外部のDNSサーバーへDNS forwardingできる機能があるため、k8s_gatewayのEXTERNAL-IPに対して、DNS forwardingできるように以下のように設定を行います。

set service dns forwarding options server=/kube.home.lab/10.0.50.202

https://help.ui.com/hc/en-us/articles/115010913367-EdgeRouter-DNS-Forwarding-Setup-and-Options

それでは、利用端末から名前解決ができるか確認します。手元の環境は、Windows OSを利用しているため、以下のコマンドで確認します。

> nslookup nginx.kube.home.lab
サーバー:  UnKnown
Address:  10.0.1.1

名前:    nginx.kube.home.lab
Address:  10.0.50.201

curlでもアクセスできることを確認します。

> curl nginx.kube.home.lab


StatusCode        : 200
StatusDescription : OK
Content           : <!DOCTYPE html>
                    <html>
                    <head>
                    <title>Welcome to nginx!</title>
                    <style>
                    html { color-scheme: light dark; }
                    body { width: 35em; margin: 0 auto;
                    font-family: Tahoma, Verdana, Arial, sans-serif; }
                    </style...
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    Accept-Ranges: bytes
                    Content-Length: 615
                    Content-Type: text/html
                    Date: Sun, 08 Jan 2023 04:18:23 GMT
                    ETag: "6398a011-267"
                    Last-Modified: Tue, 13 Dec 2022 ...
Forms             : {}
Headers           : {[Connection, keep-alive], [Accept-Ranges, bytes], [Content-Length, 615], [Content-Type, text/html]
                    ...}
Images            : {}
InputFields       : {}
Links             : {@{innerHTML=nginx.org; innerText=nginx.org; outerHTML=<A href="http://nginx.org/">nginx.org</A>; o
                    uterText=nginx.org; tagName=A; href=http://nginx.org/}, @{innerHTML=nginx.com; innerText=nginx.com;
                     outerHTML=<A href="http://nginx.com/">nginx.com</A>; outerText=nginx.com; tagName=A; href=http://n
                    ginx.com/}}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 615

最後にブラウザから https://nginx.kube.home.lab にアクセスできることを確認します。信頼されない証明書が発行されるので、ブラウザでHTTPSで接続して確認します。信頼されない証明書の警告がでるので、詳細設定からアクセスすると、Welcome to nginx!のページが表示されます。証明書のCommon Nameは Kubernetes Ingress Controller Fake Certificateになっていました。

リソースの削除

動作確認ができたので、リソースを削除しておきます。

$ kubectl delete deployment nginx
$ kubectl delete svc nginx
$ kubectl delete ingress nginx

発生した事象について

helmチャートでk8s_gatewayをインストール後に、nginxサーバーをデプロイし、 nginx.kube.home.lab というホスト名でIngressを作成後に、ホスト名でアクセスが失敗し、以下のエラーが発生することがありました。

$ curl $INGRESS_EXTERNAL_IP -H "Host: nginx.kube.home.lab"
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx</center>
</body>
</html>

k8s_gatewayの再インストールや、nginxサーバーの再デプロイで解消しましたが、詳細なログを確認できていないため、原因は不明ですが、Ingress単体ではホスト名でアクセスに成功したため、Ingressk8s_gatewayが競合していたのではと推測しています。

まとめ

k8s_gatewayプラグインを使ったDNSサーバーに連携できることで、homelabで新しいServiceをデプロイした場合でもホスト名でアクセスできるようになったため、より利便性が良くなったと思います。

参考

MetalLBとingress-nginxを使ったIngressコントローラーのインストール

前回の記事では、MetalLBを使ったロードバランサーのインストールしてみました。今回は、ingress-nginxを使ったIngressコントローラーのインストールを試してみました。

hidemium.hatenablog.com

構成

ingress-nginxのインストール

ingress-nginxは、NGINXをreverse proxyとロードバランサ―として利用するKubernetes用のIngressコントローラーになります。MetalLBと組み合わせることで、NGINX Ingress ControllerにExternal-IPを割り当てて、サービスをIngress経由で公開することができます。

以下のコマンドを実行し、ingress-nginxをインストールします。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml

ingress-nginx-controllerにEXTERNAL-IPが割り当てられたことを確認します。

$ kubectl get service ingress-nginx-controller --namespace=ingress-nginx
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.233.28.229   10.0.50.201   80:30885/TCP,443:30481/TCP   4s

ingress-nginxの動作確認

nginxサーバーをデプロイし、 nginx.kube.home.lab というホスト名でIngressを作成し、ホスト名でアクセスできることを確認します。

$ kubectl create deployment nginx --image=nginx --port=80
$ kubectl expose deployment nginx
$ kubectl create ingress nginx --class=nginx --rule nginx.kube.home.lab/=nginx:80

Ingressを作成すると、以下のエラーメッセージがでました。

error: failed to create ingress: Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": failed to call webhook: Post "https://ingress-nginx-controller-admission.ingress-nginx.svc:443/networking/v1/ingresses?timeout=10s": context deadline exceeded

ワークアラウンドとしてはingress-nginxのissueにあるようにWebhook構成を以下のように削除することで対処しました。

Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io" · Issue #5401 · kubernetes/ingress-nginx · GitHub

$ kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission

ingress-nginx-controllerがLoadBalancerで公開され、EXTERNAL-IPにMetalLBで設定したIPアドレスが付与されていることが分かります。ingressを確認すると、ホスト名とMetalLBで設定したIPアドレスが付与されていることが分かります。

$ kubectl get svc --namespace=ingress-nginx ingress-nginx-controller
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   LoadBalancer   10.233.28.229   10.0.50.201   80:30885/TCP,443:30481/TCP   9h

$ kubectl get pod,svc,ingress
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-ff6774dc6-zfhk8   1/1     Running   0          9h

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.233.0.1      <none>        443/TCP   13d
service/nginx        ClusterIP   10.233.51.128   <none>        80/TCP    9h

NAME                              CLASS   HOSTS            ADDRESS       PORTS   AGE
ingress.networking.k8s.io/nginx   nginx   nginx.kube.home.lab   10.0.50.201   80      9h

nginxサーバーに対してホスト名でアクセスできるか確認します。

$ INGRESS_EXTERNAL_IP=`kubectl get svc --namespace=ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
$ echo $INGRESS_EXTERNAL_IP
10.0.50.201
$ curl $INGRESS_EXTERNAL_IP -H "Host: nginx.kube.home.lab"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

上記の方法で通常は利用しないので、利用端末で/etc/hostsに以下のように追記をすれば利用はできますが、都度hostsファイルを編集するのは大変なため、external-dnsやCoreDNSを利用すると外部DNSと連携することが可能となるようです。

$ vi /etc/hosts
$INGRESS_EXTERNAL_IP  nginx.kube.home.lab

リソースの削除

動作確認ができたので、リソースを削除しておきます。

$ kubectl delete deployment nginx
$ kubectl delete svc nginx
$ kubectl delete ingress nginx

参考

MetalLBを使ったロードバランサーのインストール

前回の記事では、KubesprayによるKubernetesのインストールを行いました。今回は、MetalLBを使ったロードバランサーのインストールを試してみました。

hidemium.hatenablog.com

構成

MetalLBのインストール

KubesprayでMetalLBのインストールを行うことが可能なため、inventoryのgroup_varsに設定を入れていきます。

L2モードでの動作を行うため、以下のようにARPの設定を有効にします。

$ vi k8s-cluster.yml
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
# must be set to true for MetalLB, kube-vip(ARP enabled) to work
kube_proxy_strict_arp: true

以下のように、MetalLBを有効にし、MetalLBから払い出すIPアドレス帯を指定します。

$ vi addons.yml
# MetalLB deployment
metallb_enabled: true
metallb_speaker_enabled: "{{ metallb_enabled }}"
metallb_ip_range:
  - "10.0.50.201-10.0.50.254"
# metallb_pool_name: "loadbalanced"
# metallb_auto_assign: true
metallb_avoid_buggy_ips: true

クラスタ作成

masterノードとworkerノードを再作成し、前回と同様に、クラスタを構築します。

$ ansible-playbook -i inventory/mycluster/hosts.yml  --become --become-user=root cluster.yml

MetalLBがインストールされているか確認します。

$ kubectl get all -n metallb-system 
NAME                              READY   STATUS    RESTARTS   AGE
pod/controller-5ffbbd5949-pmxf9   1/1     Running   0          2d14h
pod/speaker-4d4v8                 1/1     Running   0          2d14h
pod/speaker-9f9lh                 1/1     Running   0          2d14h
pod/speaker-h8c97                 1/1     Running   0          2d14h
pod/speaker-jxwgm                 1/1     Running   0          2d14h
pod/speaker-xdbml                 1/1     Running   0          2d14h

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/speaker   5         5         5       5            5           kubernetes.io/os=linux   2d14h

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           2d14h

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-5ffbbd5949   1         1         1       2d14h

MetalLBの動作確認

以下のように、nginxをデプロイします。

$ kubectl create deployment nginx --image=nginx

デプロイ後の状態は以下の通りです。

$ kubectl get pod,svc
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-76d6c9b8c-4cqrc   1/1     Running   0          17s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.233.0.1   <none>        443/TCP   4h15

デプロイしたnginxをserviceとして公開します。この時、 typeにLoadBalancerを指定します。

$ kubectl expose deployment nginx --port 80 --type LoadBalancer

nginxがLoadBalancerで公開され、EXTERNAL-IPにMetalLBで設定したIPアドレスが付与されていることが分かります。

$ kubectl get pod,svc
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-76d6c9b8c-4cqrc   1/1     Running   0          43s

NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP      10.233.0.1      <none>        443/TCP        4h16m
service/nginx        LoadBalancer   10.233.51.192   10.0.50.201   80:31592/TCP   2s

nginxに対してEXTERNAL-IPでアクセスできるか確認します。

$ INGRESS_EXTERNAL_IP=`kubectl get svc nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
$ curl $INGRESS_EXTERNAL_IP
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

MetalLBのL2モード(ARP)の動作

MetalLB には、ControllerとSpeakerの 2 つのコンポーネントが含まれています。Controllerはmasterノードにデプロイされ、SpeakerはDaemonsetモードでworkerノードの各ノードにデプロイされます。

Controllerは、事前に定義したIPプールからIPアドレスの割り当てを管理し、Speakerは、選択したモード(L2モード(ARP/NDP)、BGP)に従ってPodへの通信ができるようにします。

今回利用したL2モード(ARP)では、1 つのノードがサービスをローカル ネットワークに通知する役割を担います。ノードを確認すると、ネットワーク インターフェイスに複数の IP アドレスが割り当てられているように見えます。

ARP要求が来た場合、SpeakerがARPのリプライを返します。

また、サービス IP のすべてのトラフィックが 1 つのノードに送信されます。そこから kube-proxyを経由してトラフィックをすべてのサービスのポッドに分散します。

ARP要求とARPリプライの挙動について確認してみます。

EXTERNAL-IPに対して、arpingを実行します。

$ sudo arping -I ens192 10.0.50.201

ARP応答を行うノードについて以下のコマンドで確認します。

$ kubectl describe service nginx
Events:
  Type    Reason        Age                From                Message
  ----    ------        ----               ----                -------
  Normal  IPAllocated   37m                metallb-controller  Assigned IP ["10.0.50.201"]
  Normal  nodeAssigned  33m (x2 over 37m)  metallb-speaker     announcing from node "kube-worker01"

ARP応答を行うノードで、tcpdumpを行い、ARP要求とARP応答が行われていることを確認します。

$ sudo tcpdump -n -i ens192 arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on ens192, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:33:19.405011 ARP, Request who-has 10.0.50.201 tell 10.0.50.163, length 46
22:33:19.405155 ARP, Reply 10.0.50.201 is-at 00:50:56:88:f9:61, length 46
22:33:20.406228 ARP, Request who-has 10.0.50.201 tell 10.0.50.163, length 46
22:33:20.406378 ARP, Reply 10.0.50.201 is-at 00:50:56:88:f9:61, length 46
22:33:21.406975 ARP, Request who-has 10.0.50.201 tell 10.0.50.163, length 46
22:33:21.407122 ARP, Reply 10.0.50.201 is-at 00:50:56:88:f9:61, length 46

リソースの削除

動作確認ができたので、リソースを削除しておきます。

kubectl delete deployment nginx
kubectl delete svc nginx

参考

KubesprayによるKubernetesのインストール

Kubernetesの環境を触ってみなくなり、Kubernetesのインストール方法でKubesprayが便利そうだったので、KubesprayによるKubernetesのインストールを試してみました。

構成

Kubesprayのダウンロード

Kubesprayをダウンロードします。

git clone https://github.com/kubernetes-sigs/kubespray

Ansibleのインストール

こちらの手順を確認しながら、venvの環境をインストールし、Ansibleをインストールしていきます。

kubespray/ansible.md at master · kubernetes-sigs/kubespray · GitHub

$ VENVDIR=kubespray-venv
$ KUBESPRAYDIR=kubespray
$ ANSIBLE_VERSION=2.12
$ python3 -m venv $VENVDIR
$ source $VENVDIR/bin/activate
$ cd $KUBESPRAYDIR
$ pip install -U -r requirements-$ANSIBLE_VERSION.txt
$ test -f requirements-$ANSIBLE_VERSION.yml && \
  ansible-galaxy role install -r requirements-$ANSIBLE_VERSION.yml && \
  ansible-galaxy collection -r requirements-$ANSIBLE_VERSION.yml

インベントリの作成

インベントリを作成していきます。リポジトリの中のsampleディレクトリをコピーし、自身の環境向けのhostファイルやgroup_varsを用意できます。

cp -r inventory/sample inventory/mycluster
declare -a IPS=(10.0.50.30 10.0.50.31 10.0.50.32 10.0.50.33 10.0.50.34)
CONFIG_FILE=inventory/mycluster/hosts.yml python3 contrib/inventory_builder/inventory.py ${IPS[@]}

hosts.ymlファイルが生成されますが、構成を変えたいので、以下のように変更します。

今回はmasterノード3台とworkerノード2台にしています。

$ vi hosts.yml
all:
  hosts:
    kube-master01:
      ansible_host: 10.0.50.30
      ip: 10.0.50.30
      access_ip: 10.0.50.30
    kube-master02:
      ansible_host: 10.0.50.31
      ip: 10.0.50.31
      access_ip: 10.0.50.31
    kube-master03:
      ansible_host: 10.0.50.32
      ip: 10.0.50.32
      access_ip: 10.0.50.32
    kube-worker01:
      ansible_host: 10.0.50.33
      ip: 10.0.50.33
      access_ip: 10.0.50.33
    kube-worker02:
      ansible_host: 10.0.50.34
      ip: 10.0.50.34
      access_ip: 10.0.50.34
  children:
    kube_control_plane:
      hosts:
        kube-master01:
        kube-master02:
        kube-master03:
    kube_node:
      hosts:
        kube-worker01:
        kube-worker02:
    etcd:
      hosts:
        kube-master01:
        kube-master02:
        kube-master03:
    k8s_cluster:
      children:
        kube_control_plane:
        kube_node:
    calico_rr:
      hosts: {}

masterノードとworkerノードのデプロイ

以前、cloud-initを利用した仮想マシンのカスタマイズを書きましたが、Kubernetesの再作成が簡単になるため利用します。

hidemium.hatenablog.com

Ubuntu 22.04のテンプレートを作成し、以下のスペックでテンプレートを用意しておきます。

  • CPU: 1vCPU
  • Memory: 4GB
  • Disk: 100GB

masterノードとworkerノードのスペックはそれぞれの以下の通りになります。

node vCPU Memory Disk
master 1 4GB 100GB
worker 1 4GB 100GB

cloud-initでmasterノードとworkerノードのIPアドレスと公開鍵を設定を行っていきます。

IPアドレスを設定するために、以下のようにmetadataを作成します。こちらは仮想マシンごとにファイルを作成しておきます。

$ vi metadata-kube-master01.yaml
instance-id: kube-master01
local-hostname: kube-master01
hostname: kube-master01
network:
  version: 2
  ethernets:
    ens192:
      dhcp4: false
      addresses:
      - 10.0.50.30/24
      gateway4: 10.0.50.1
      nameservers:
        addresses:
        - 10.0.50.1

公開鍵を設定するために、以下のようにuserdataを作成します。こちらは全体で共通のファイルを作成しておきます。

$ vi userdata-kube.yaml
#cloud-config

users:
  - default
  - name: username
    ssh_authorized_keys:
      - 公開鍵をペースト
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo, wheel
    lock_passwd: true
    shell: /bin/bash

firewallは閉じられているため変更はしていません。

$ sudo ufw status
Status: inactive

govcによるメタデータの追加

govcを利用するための環境変数を設定します。

$ export GOVC_URL=https://<vcenter fqdn>/sdk
$ export GOVC_USERNAME="username"
$ export GOVC_PASSWORD="password"
$ export GOVC_INSECURE="1"

masterノードとworkerノードごとに以下のコマンドを実行し、masterノードとworkerノードの仮想マシンをデプロイします。

$ export VMNAME="kube-master01"
$ export VM="/Datacenter/vm/path/to/$VMNAME"
$ govc vm.info "${VM}"
$ export METADATA=$(gzip -c9 <metadata-${VMNAME}.yaml | { base64 -w0 2>/dev/null || base64; }) USERDATA=$(gzip -c9 <userdata-kube.yaml | { base64 -w0 2>/dev/null || base64; })
$ govc vm.change -vm "${VM}" -e guestinfo.metadata="${METADATA}" -e guestinfo.metadata.encoding="gzip+base64" -e guestinfo.userdata="${USERDATA}" -e guestinfo.userdata.encoding="gzip+base64"
$ govc vm.power -on "${VM}"

以下のようにコマンドを実行すれば、まとめて仮想マシンをデプロイできます。

$ for VMNAME in kube-master0{1..3}; do
  echo ${VMNAME}
  export VM="/Datacenter/vm/path/to/$VMNAME"
  govc vm.clone -vm template-vm -on=false -folder="/Datacenter/vm/k8s" $VMNAME
  export METADATA=$(gzip -c9 <metadata-${VMNAME}.yaml | { base64 -w0 2>/dev/null || base64; }) USERDATA=$(gzip -c9 <userdata-kube.yaml | { base64 -w0 2>/dev/null || base64; })
  govc vm.change -vm "${VM}" -e guestinfo.metadata="${METADATA}" -e guestinfo.metadata.encoding="gzip+base64" -e guestinfo.userdata="${USERDATA}" -e guestinfo.userdata.encoding="gzip+base64"
  govc vm.power -on "${VM}"
done
$ for VMNAME in kube-worker0{1..2}; do
  echo ${VMNAME}
  export VM="/Datacenter/vm/path/to/$VMNAME"
  govc vm.clone -vm template-vm -on=false -folder="/Datacenter/vm/k8s" $VMNAME
  export METADATA=$(gzip -c9 <metadata-${VMNAME}.yaml | { base64 -w0 2>/dev/null || base64; }) USERDATA=$(gzip -c9 <userdata-kube.yaml | { base64 -w0 2>/dev/null || base64; })
  govc vm.change -vm "${VM}" -e guestinfo.metadata="${METADATA}" -e guestinfo.metadata.encoding="gzip+base64" -e guestinfo.userdata="${USERDATA}" -e guestinfo.userdata.encoding="gzip+base64"
  govc vm.power -on "${VM}"
done

Kubesprayによるクラスタ構築

以下のコマンドでKubernetesクラスタを構築することができます。

$ ansible-playbook -i inventory/mycluster/hosts.yml  --become --become-user=root cluster.yml

以下のようにログが出力され無事構築が完了します。30分程度で構築が完了します。

TASK [network_plugin/calico : Check if inventory match current cluster configuration] **************************************
ok: [kube-master01] => {
    "changed": false,
    "msg": "All assertions passed"
}
Thursday 22 December 2022  07:56:58 +0900 (0:00:00.101)       0:33:20.265 ***** 
Thursday 22 December 2022  07:56:58 +0900 (0:00:00.060)       0:33:20.325 ***** 
Thursday 22 December 2022  07:56:58 +0900 (0:00:00.060)       0:33:20.386 ***** 

PLAY RECAP *****************************************************************************************************************
kube-master01              : ok=744  changed=146  unreachable=0    failed=0    skipped=1270 rescued=0    ignored=9   
kube-master02              : ok=652  changed=135  unreachable=0    failed=0    skipped=1110 rescued=0    ignored=4   
kube-master03              : ok=654  changed=136  unreachable=0    failed=0    skipped=1108 rescued=0    ignored=4   
kube-worker01              : ok=507  changed=93   unreachable=0    failed=0    skipped=775  rescued=0    ignored=2   
kube-worker02              : ok=507  changed=93   unreachable=0    failed=0    skipped=775  rescued=0    ignored=2   
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Thursday 22 December 2022  07:56:58 +0900 (0:00:00.189)       0:33:20.576 ***** 
=============================================================================== 
download : download_container | Download image if required --------------------------------------------------------- 55.77s
download : download_file | Download item --------------------------------------------------------------------------- 51.95s
download : download_container | Download image if required --------------------------------------------------------- 51.70s
network_plugin/calico : Wait for calico kubeconfig to be created --------------------------------------------------- 47.02s
kubernetes/control-plane : Joining control plane node to the cluster. ---------------------------------------------- 46.59s
container-engine/containerd : download_file | Download item -------------------------------------------------------- 28.89s
download : download_file | Validate mirrors ------------------------------------------------------------------------ 28.20s
download : download_file | Download item --------------------------------------------------------------------------- 27.57s
download : download_container | Download image if required --------------------------------------------------------- 26.08s
kubernetes/control-plane : kubeadm | Initialize first master ------------------------------------------------------- 25.96s
kubernetes/preinstall : Preinstall | wait for the apiserver to be running ------------------------------------------ 24.94s
kubernetes/preinstall : Install packages requirements -------------------------------------------------------------- 24.79s
kubernetes/preinstall : Update package management cache (APT) ------------------------------------------------------ 24.47s
etcd : reload etcd ------------------------------------------------------------------------------------------------- 23.30s
download : download_file | Download item --------------------------------------------------------------------------- 23.14s
kubernetes/kubeadm : Join to cluster ------------------------------------------------------------------------------- 21.30s
download : download_container | Download image if required --------------------------------------------------------- 20.24s
container-engine/crictl : download_file | Download item ------------------------------------------------------------ 20.20s
download : download_container | Download image if required --------------------------------------------------------- 20.11s
download : download_container | Download image if required --------------------------------------------------------- 17.82s

kubectlのインストール

Ansibleの実行ノードにkubectlをインストールしていきます。

$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
OK
$ sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
$ sudo apt update
$ sudo apt install kubectl

masterノードから /etc/kubernetes/admin.conf を取得します。

admin.confを取得するために、masterノードにログインし、ファイルの権限を変更しておきます。

$ ssh $USERNAME@$IP_CONTROLLER_0
$ USERNAME=$(whoami)
$ sudo chown -R $USERNAME:$USERNAME /etc/kubernetes/admin.conf
$ exit

masterノードから /etc/kubernetes/admin.conf を取得します。

scp -v $USERNAME@$IP_CONTROLLER_0:/etc/kubernetes/admin.conf kubespray-do.conf

serverのIPアドレスをmasterノードのIPアドレスに書き換えを行います。

$ vi kubespray-do.conf
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: XXX
    server: https://10.0.50.30:6443  # 書き換え
  name: cluster.local
...

設定をロードして、接続ができることを確認します。

$ export KUBECONFIG=$PWD/kubespray-do.conf
$ kubectl get nodes
NAME            STATUS   ROLES           AGE   VERSION
kube-master01   Ready    control-plane   23h   v1.25.5
kube-master02   Ready    control-plane   23h   v1.25.5
kube-master03   Ready    control-plane   23h   v1.25.5
kube-worker01   Ready    <none>          23h   v1.25.5
kube-worker02   Ready    <none>          23h   v1.25.5

スモークテスト

動作確認を行います。

テスト用のpodを2台作成し、pod間で疎通が取れるか確認します。

$ kubectl run myshell1 -it --rm --image busybox -- sh
/ # hostname -i
10.233.68.2
/ # ping 10.233.92.3
PING 10.233.92.3 (10.233.92.3): 56 data bytes
64 bytes from 10.233.92.3: seq=0 ttl=62 time=0.659 ms
64 bytes from 10.233.92.3: seq=1 ttl=62 time=0.419 ms
^C
--- 10.233.92.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.419/0.539/0.659 ms
kubectl run myshell2 -it --rm --image busybox -- sh
/ # hostname -i
10.233.92.3

/ # ping 10.233.68.2
PING 10.233.68.2 (10.233.68.2): 56 data bytes
64 bytes from 10.233.68.2: seq=0 ttl=62 time=1.144 ms
64 bytes from 10.233.68.2: seq=1 ttl=62 time=0.489 ms
64 bytes from 10.233.68.2: seq=2 ttl=62 time=0.402 ms
^C
--- 10.233.68.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.402/0.678/1.144 ms

podの状態は以下の通りです。

$ kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP            NODE            NOMINATED NODE   READINESS GATES
myshell1   1/1     Running   0          4m34s   10.233.68.2   kube-worker01   <none>           <none>
myshell2   1/1     Running   0          7s      10.233.92.3   kube-worker02   <none>           <none>

デプロイメント

nginxのdeploymentを作成します。

port-forwardを行い、nginxへcurlでアクセスができることを確認します。

$ kubectl create deployment nginx --image=nginx
$ kubectl get pods -l app=nginx
NAME                    READY   STATUS      RESTARTS   AGE
nginx-76d6c9b8c-kt6td   1/1     Running     0          13m
$ POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")
$ kubectl port-forward $POD_NAME 8080:80
$ curl --head http://127.0.0.1:8080

動作確認ができため、deploymentを削除しておきます。

$ kubectl delete deployment nginx

リセット

以下のコマンドでKubernetesクラスタをリセットすることができます。

$ ansible-playbook -i inventory/mycluster/hosts.yml --become --become-user=root reset.yml

リセット後にKubesprayで再構築を行いましたが、再構築に失敗したため、仮想マシンごと削除して、govcで仮想マシンの再作成を行う行うことで、Kubernetesクラスタを何度も構築することができます。

$ govc vm.destroy "${VM}"

参考