Zalando Postgres Operator を試してみた
ふとしたきっかけで、自宅ラボに PostgreSQL as a Service が欲しくなったので Zalando Postgres Operator を試してみました。
Zalando Postgres Operator とは
Zalando Postgres Operator(以下、Postgres Operator)とは、Kubernetes クラスタに HA 構成の PostgreSQL クラスタを簡単に構築してくれる Operator です。
Postgres Operator を Kubernetes クラスタにデプロイすると、postgresql
というカスタムリソースを宣言したマニフェストを使うだけで簡単に PostgreSQL クラスタを構築できます。
また、専用の Web UI を使って PostgreSQL クラスタを管理することもできます。
この Postgres Operator を自宅ラボの Kubernetes クラスタにデプロイして PostgreSQL as a Service として利用します。
Zalando SE
Zalando SE とは、ヨーロッパを中心に展開するオンラインファッションプラットフォームを運営する企業です(日本で言うところの ZOZO でしょうか?)。
Zalando SE は GitHub で複数のオープンソースのプロジェクトを公開しています。
Zalando Postgres Operator も Zalando が開発しているプロジェクトの一つです。
構成
公式ドキュメント の画像を拝借させてもらうのですが、
Postgres Operator デプロイ後に postgresql
カスタムリソースをデプロイすると、下図のリソースが Postgres Operator によって作成されます。
HA 構成の PostgreSQL クラスタを構築するため、Pod には Spilo というコンテナイメージが使用されます。
Spilo コンテナの中では PostgreSQL と Patroni というサービスが動いています。
Patroni は PostgreSQL クラスタのノードを監視して、マスタが故障した際に自動フェイルオーバーを実現するためのサービスです。
Spilo と Patroni は共に Zalando のプロジェクトとなっています。
今回初めて Zalando を知りましたが、ほとんど自分たちで開発していてすごいですね…。
インストール
Postgres Operator のインストール方法には以下の3通りあります。
- マニュアル
- Kustomize
- Helm
今回はマニュアル、つまりマニフェストをデプロイする方法で Postgres Operator をインストールします。
公式リポジトリ内のマニフェストを kubectl apply
するだけで Operator と Web UI をインストールできます。
$ git clone https://github.com/zalando/postgres-operator.git $ cd postgres-operator/ $ kubectl create namespace zalando-postgres # Postgres Operator $ kubectl apply -f manifests/configmap.yaml $ kubectl apply -f manifests/operator-service-account-rbac.yaml # namespace: defualt のものがあるので注意 $ kubectl apply -f manifests/postgres-operator.yaml $ kubectl apply -f manifests/api-service.yaml # UI $ kubectl apply -f ui/manifests/ # 確認 $ kubectl get all -n zalando-postgres NAME READY STATUS RESTARTS AGE pod/postgres-operator-f6bfcd5b4-zsh2n 1/1 Running 0 3d7h pod/postgres-operator-ui-6dc47bbbc6-phdvw 1/1 Running 0 4d9h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/postgres-operator ClusterIP 10.96.100.47 <none> 8080/TCP 6d23h service/postgres-operator-ui LoadBalancer 10.104.183.177 192.168.2.252 80:31363/TCP 14d NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/postgres-operator 1/1 1 1 6d23h deployment.apps/postgres-operator-ui 1/1 1 1 14d NAME DESIRED CURRENT READY AGE replicaset.apps/postgres-operator-f6bfcd5b4 1 1 1 6d23h replicaset.apps/postgres-operator-ui-6dc47bbbc6 1 1 1 13d
postgres-operator
と postgres-operator-ui
の Pod がデプロイされました。
注意点 1
manifests/operator-service-account-rbac.yaml には namespace: default
と指定されているため、Postgres Operator 用の namespace を default 以外にする場合には変更が必要です。
# manifests/operator-service-account-rbac.yaml apiVersion: v1 kind: ServiceAccount metadata: name: postgres-operator namespace: zalando-postgres
注意点 2
Web UI のマニフェストはそのままでは namespace: default
しか管理対象となりません。
全 namespace を対象とする場合は、ui/manifests/deployment.yaml の TARGET_NAMESPACE
に "*" を指定します。
# ui/manifests/deployment.yaml env: - name: "TARGET_NAMESPACE" value: "default" # "*" に変更
以上で Postgres Operator のインストールは完了です。
PostgreSQL クラスタ構築
Postgres Operator の準備ができたので、PostgreSQL クラスタを構築していきます。 クラスタ構築には2通りあります。
- Web UI
- マニュアル
Web UI
さきほど作成した postgres-operator-ui
に Service または Ingress 経由でアクセスすると、クラスタ作成画面が表示されます。
必要な項目を入力して「Create Cluster」を押すと PostgreSQL クラスタが作成されます。
マニュアル
マニュアルの場合は、postgresql リソースを宣言したマニフェストを準備します。
公式リポジトリのサンプルや、Web UI のクラスタ作成画面に表示されるマニフェストが参考になります。
以下は自宅ラボの k8s クラスタにデプロイしたお試しマニフェストです。 Rook で用意した storageClass を追加しています。
# postgres.yaml apiVersion: acid.zalan.do/v1 kind: postgresql metadata: name: acid-example namespace: default labels: team: acid spec: teamId: acid postgresql: version: "13" # note: String numberOfInstances: 1 volume: size: 1Gi storageClass: rook-ceph-block users: example: [] databases: example: example allowedSourceRanges: resources: requests: cpu: 100m memory: 100Mi limits: cpu: 500m memory: 500Mi
postgresql リソースのデプロイが完了すると、Postgres Operator により StatefulSet や Service の各種リソースが作成されます。
$ kubectl get all NAME READY STATUS RESTARTS AGE pod/acid-example-0 1/1 Running 0 164m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/acid-example ClusterIP 10.98.124.54 <none> 5432/TCP 7d service/acid-example-config ClusterIP None <none> <none> 7d service/acid-example-repl ClusterIP 10.106.121.165 <none> 5432/TCP 7d NAME READY AGE statefulset.apps/acid-example 1/1 7d NAME TEAM VERSION PODS VOLUME CPU-REQUEST MEMORY-REQUEST AGE STATUS postgresql.acid.zalan.do/acid-example acid 13 1 1Gi 100m 100Mi 7d Running
注意点
Web UI の注意点として、volume.storageClass の指定ができません。
そのため、Rook Ceph で作成した storageClass などを指定する場合はマニフェストを使う必要があります。
Web UI では基本的な情報を埋め込んだマニフェストを作成し、細かい設定はマニフェストに直接記述するのが良さそうです。
接続確認
kubectl port-forward
を使い、ローカル環境から PostgreSQL クラスタへ接続できるか確認します。
本当はフェイルオーバーなど HA 構成の特性を検証してみたかったのですが、PostgreSQL について勉強不足のため実施していません。
Postgres Operator によりログイン用のパスワードが Secret として作成されています。
$ export PGMASTER=$(kubectl get pods -o jsonpath={.items..metadata.name} -l application=spilo) $ kubectl port-forward $PGMASTER 6432:5432 & $ kubectl get secret postgres.acid-example.credentials -o 'jsonpath={.data.password}' | base64 -d $ export PGSSLMODE=require $ psql -U postgres -h localhost -p 6432 Handling connection for 6432 ユーザ postgres のパスワード: Handling connection for 6432 psql (13.1) SSL 接続 (プロトコル: TLSv1.2、暗号化方式: ECDHE-RSA-AES256-GCM-SHA384、ビット長: 256、圧縮: オフ) "help"でヘルプを表示します。 postgres=#
無事に Postgres Operator で作成した PostgreSQL クラスタに接続することができました。
参考にさせていただいたサイト
今回 PostgreSQL as a Service を構築するにあたって情報を提供くださった @tzkb さんのスライドです。
そもそもの「Kubernetes 上でデータベースを動かす際の課題」から、より深い話まで説明されています。
仕事では SQL Server メインなのですが、Database on Kubernetes という観点では課題は同じはずですので、とても勉強になりました。
Zalando Postgres Operator の紹介をされている記事です。
今回のネタは、ほぼ上記記事の内容を元にしています。 フェイルオーバーなどのクラスタ構成の検証もされていて興味深かったです。
Patroni を試されている記事です。
自分は Patroni が何者か分からない状態だったので参考になりました。
さいごに
PostgreSQL as a Service のための Zalando Postgres Operator の使い方について確認しました。
PostgreSQL については本当に初心者なのですが、そのような状態でも Kubernetes クラスタがあれば簡単に PostgreSQL as a Service が実現できました。
もし手元に Kubernetes クラスタをお持ちなら、クラウドサービスではなく Zalando Postgres Operator も選択肢に入れてみると面白いかもしれません。
2020年振り返り
2020年も残すところ数時間なので、今年の振り返りをサクッとします。
第一子誕生
1月に長男が誕生しました。
2, 3ヶ月は夜に起こされることも多く心身ともに疲労がありましたが、 妻の実家に近いこともあり、かなり助けられました。
大変な時期も含めて、とてもとても可愛くて毎日の成長が楽しみです。
来週には1歳になりますが、まだ誕生日プレゼントを決めきれずにいます。 年始のセールで決めちゃいましょう。
自宅ラボ構築
5月に自宅ラボを構築しました。
今でも十分に大活躍していますが、仮想化基盤としてはメモリ不足を感じているのでスケールアウトかスケールアップしたいですね。 (沼の気配がする)
初 LT (オンライン)
6月の Ansible Night で初めてのLTをしました。
Ansible コミュニティ (ansiblejp) では新型コロナが広まる前から、オンラインでのイベント配信やもくもく会が開催されていました。 地方住みということもあり、Ansible コミュニティイベントは参加の敷居が低く、モチベーションを維持しやすかったです。
そこで色々な方と出会い(主にTwitterで)刺激を受けていく中で、いつしか「LTをしてみたい」という気持ちが湧いてきました。 ちょうど新型コロナの影響で Ansible Night がオンラインで開催されることになったので思い切ってエントリーしてみました。
Ansible Night オンライン! 2020.06 - connpass
なんとか無事(?)にLTをやり遂げたのですが、発表内容やプレゼンの仕方についてはまだまだ未熟だと自身でも感じたので、 なるべく機会を見つけてLTなどの経験を積んでいきたいです。
初オンライン飲み
7月に初めてオンラインで飲み会をしました。
Twitterで仕事の愚痴をつぶやいていたところ、Ansible 関連で知り合った(フォローし合った)方からオンライン飲みに誘われました。
初めて喋る方とする初めてのオンライン飲み、始める前は緊張しかなかったのです。 しかし、お酒の力や参加された方々の人柄もあり、終盤はいつもの飲み会と変わらない楽しさがありました。 (その節は愚痴を聞いていただき本当にありがとうございました)
その後も何度か誘っていただき、オンラインならではの「移動が必要ない」とか「安上がり」といったメリットを実感しました。 今後、コロナが収まっても居酒屋に行く機会は減りそうです。
マイホーム
7月に家が建ちました。
2019年3~9月あたりで間取りなどをハウスメーカーの担当さんと計画していたのですが、この時期に妊娠発覚して後半はほぼメーカーにお任せ状態でした。
ところどころ「ここにコンセント欲しかった」とか「冷蔵庫はここじゃなかった」とか、もう少し考えておけばという後悔もありますが、住めば都です。
書斎も作らせてもらったので、今後テレワークになったとしても快適に仕事ができるように「城」を築いていこうと思います。
2回目のLT
12月の Kubernetes Meetup Novice で2回目のLTをしました。
Kubernetes Novice Tokyo #7 - connpass
Kubernetes 関連は仕事では触れる機会がなく完全に趣味レベルではあったのですが、 こちらのコミュニティでは自分のような「Kubernetes初心者」の登壇機会を提供してくださっています。
そして今回のテーマが「○○試してみた」ということで、ちょうど11月に触っていた ExternalDNS をネタにして登壇しました。
初回に比べればまだマシになったかな?という程度でまだまだ経験値が足りないと反省しました。
総括
2020年は人生としても大きなイベントがあり、初めての経験もたくさんできました。
繋がってくださった方々、機会をいただいた方々、助けてくださった方々にはとても感謝しています。 本当にありがとうございました。
2021年はアウトプットをたくさんして、自分から機会を提供できればと考えています。
AKS にスポットノードプールを追加してみた
AKS (Azure Kubernetes Service) にスポットノードプールを追加してみました。
以下のドキュメントを参考にしました。
動機
スポットノードプールを試した理由ですが、AKS を安く使えると思ったから です。
AKS は最低でも 1 台のノードを含む 1 つのノードプールが必要です。 この 1 台分の料金をスポットノードプールを使うことで、スポット料金が適用されて通常のノードを使うよりも安く済むと考えました。
結論から言うと、できません!安くなりません!
スポットノードプール
スポットノードプールとは
スポット VM という、Azure の余っているリソースを通常より安く利用できる VM を AKS で利用するノードプールです。 安く利用できる反面、Azure のリソースが不足してきたら自動で VM は排除されてしまいます。
AKS でのスポットノードプールの利用は、バッチ処理ジョブ や 開発・テスト環境 といったワークロードが適しているようです。
制限事項
スポットノードプールにはいくつかの制限事項があります。 ここでは一部だけ抜粋して紹介しますので、詳しくは公式ドキュメントを参照ください。
この時点で、「AKS を安く使う」という動機が満たせないことが分かりました…。 とりあえず、AKS クラスタにスポットノードプールを追加することは試してみました。
AKS クラスタにスポットノードプール追加
AKS クラスタ構築
まずは AKS クラスタを構築します。
今回はノードプールを最小構成 サイズ:Standard_B2S, ノード数:1
で構築しました。
$ az aks create \ --resource-group aks \ --name aks-test \ --location japaneast \ --kubernetes-version 1.19.3 \ --node-vm-size Standard_B2S \ --node-count 1 \ --no-wait
構築完了後は、ノードプールが 1 つだけ作成されています。
このノードプールで使用しているノードサイズ Standard_B2S
は 月額 ¥4,447.744 です。
もしスポット VM として利用できれば、60% オフの 月額 ¥1,779.0976 となります。
スポットノードプール追加
次に、構築した AKS クラスタにスポットノードプールを追加します。
公式ドキュメントベースの設定で az aks nodepool add
コマンドを実行します。
コマンド自体は通常のノードプールを追加するものと同じです。
--priority
, --eviction-policy
, --spot-max-price
がスポットノードプール用のオプションになります。
az aks nodepool add \ --resource-group aks \ --cluster-name aks-test \ --name spotnodepool \ --priority Spot \ --eviction-policy Delete \ --spot-max-price -1 \ --enable-cluster-autoscaler \ --min-count 1 \ --max-count 3 \ --no-wait
spotnodepool
という名前のノードプールが追加されました。(画像はプール作成途中です)
追加されたスポットノードプールの状態を見てみると、 サイズ:Standard_DS2_v2, ノード数:3
になっています。
コマンドでノードサイズを指定しない場合はデフォルトの Standard_DS2_v2
になります。
オプションの --enable-cluster-autoscaler
で AKS のクラスタオートスケーラを有効化しており、
オートスケーラによる最大ノード数を --max-count
で指定しているためノード数が 3 となっています。
Standard_DS2_v2
のスポット料金は通常の 78% オフとなりますが、Standard_B2S
と比べると約 2 倍の料金となります。
また、ノード数も 3 となっているので 3 台分の料金が掛かります。
これでは、せっかくスポットノードプールを使っていても高くついてしまうため、
既定のノードプールと同じく サイズ:Standard_B2S, ノード数:1
でスポットノードプールを作ってみます。
Standard_B2S のスポットノードプール追加
まずは、ノードサイズに Standard_B2S
を指定します。
az aks nodepool add \ --resource-group aks \ --cluster-name aks-test \ --name spotnodepool \ --priority Spot \ --eviction-policy Delete \ --spot-max-price -1 \ --enable-cluster-autoscaler \ --min-count 1 \ --max-count 3 \ --node-vm-size Standard_B2S \ # このオプションを追加 --no-wait
上記コマンドを実行するとスポットノードプールの作成は始まるのですが、 最終的には Failed となってしまいました。
Azure に Standard_B2S 用のリソースが余ってないということでしょうか。 執筆時点では原因は分かっていません…。
ノード数 1 のスポットノードプール追加
次に、ノード数が 1 となるように指定します。
az aks nodepool add \ --resource-group aks \ --cluster-name aks-test \ --name spotnodepool \ --priority Spot \ --eviction-policy Delete \ --spot-max-price -1 \ --enable-cluster-autoscaler \ --min-count 1 \ --max-count 1 \ # 3 → 1 に変更 --no-wait
上記コマンドを実行すると node-count is not in the range of min-count and max-count
というエラーになりました。
どうやら --min-count
と --max-count
は同じ値にしてはいけないようです。
--node-count
オプションでノード数を指定してみます。
az aks nodepool add \ --resource-group aks \ --cluster-name aks-test \ --name spotnodepool \ --priority Spot \ --eviction-policy Delete \ --spot-max-price -1 \ --enable-cluster-autoscaler \ --min-count 1 \ --max-count 3 \ --node-count 1 \ # ノード数指定 --no-wait
この指定方法だとノード数を 1 にできました。
まとめ
スポットノードプールを使って AKS を安く使う計画はうまくいきませんでした。
しかし、ノードの冗長構成を検証するなどであれば、通常のノードプールを使うよりも費用は抑えられ、 勝手にノードが削除されることを利用した可用性の確認などもできるかもしれません。
自宅 k8s クラスタのサービスに FQDN で繋がるようにした
自宅の検証用マシン (Deskmini A300) に ESXi を入れて検証環境として利用しています。
最近はそこへ k8s クラスタを構築して色々試しているのですが、クラスタ内に立ち上げたサービスへは IP アドレスでアクセスしていました。 IP アドレスでアクセスするのはとても面倒だったのですが、やっと k8s で動かしているサービスに FQDN で繋がるようになったので投稿します。
システム構成図
完成後のシステム構成図になります。 (構成図描くの下手で分かりにくいと思います…)
見てもらって分かる通り、LAN 用の DNS サーバを VM で建てています (k8s クラスタの外です)。 これは、k8s クラスタを構築する前に DNS サーバ (dnsmasq) を構築していて、それを流用しているためです。
使用するプロダクト
今回利用しているプロダクトは以下になります。
名前 | バージョン | 用途 |
---|---|---|
Kubernetes | 1.19.0 | コンテナオーケストレーション |
Docker | 19.03.12 | コンテナランタイム |
ExternalDNS | 0.7.3 | DNS プロバイダに DNS を登録 |
CoreDNS | 1.8.0 | DNS サーバ |
etcd | 3.4.13 | DNS レコード格納 |
MetalLB | 0.9.3 | ベアメタルロードバランサー |
構築
以下の手順で環境を構築しました。
- DNS サーバ (CoreDNS & etcd) 構築
- MetalLB デプロイ
- ExternalDNS デプロイ
DNS サーバ (CoreDNS & etcd) 構築
CoreDNS & etcd を使った DNS サーバを構築します。 この DNS サーバに k8s クラスタのサービス用の DNS レコードを格納していきます。 なぜ dnsmasq を流用しないかというと、後述する ExternalDNS が dnsmasq に対応していないからです。
今までは dnsmasq を yum でインストールしていたのですが、今回からは Docker コンテナを使っていきます。 docker のインストールは省略します。 (Docker Compose を使えばよかったかも)
コンテナネットワーク
CoreDNS コンテナから etcdコンテナへ繋がるようにするために dns-network
という名前の bridge を作成しておきます。
$ docker network create --driver bridge dns-network
CoreDNS
CoreDNS とは、Go 言語で書かれた DNS サーバで、 CNCF によってホストされているプロジェクトです。 プラグイン形式で DNS レコードの格納/参照先を柔軟に設定できるところが売りなようです。
今回は etcd プラグインと hosts プラグインを使って、「k8s のサービスは etcd、その他は /etc/hosts に直書き」という処理をさせます。
まずは CoreDNS の設定ファイル Corefile
を準備します。
仮に example.com
をローカルで使用するドメインとして、example.com
の問い合わせは以下の順番で確認するようにします。
- etcd (k8s サービス用)
- /etc/hosts
example.com
以外の問い合わせは /etc/resolv.conf
を参照して外部の DNS サーバに回します。
#Corefile example.com { etcd { path /skydns endpoint http://etcd:2379 fallthrough } hosts cache errors log } . { forward . /etc/resolv.conf }
次に、CoreDNS コンテナを起動します。
先ほど作成した Corefile
と /etc/hosts
をマウントしています。
$ docker run -d \ --name coredns \ --network dns-network \ -v /home/nnstt1/Corefile:/root/Corefile \ -v /etc/hosts:/etc/hosts \ -p 53:53/udp \ -p 53:53/tcp \ coredns/coredns:1.8.0 -conf /root/Corefile
etcd
etcd とは、こちらも Go 言語で書かれた分散 KVS (Key Value Store) です。 今回は CoreDNS で管理する DNS レコードの格納先としてシングルノードで動かします。
こちらは設定ファイルの用意はなく、etcd コンテナを起動するだけです。
$ mkdir -p /tmp/etcd-data.tmp $ docker run -d \ -p 2379:2379 \ -p 2380:2380 \ --mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \ --name etcd \ --network dns-network \ gcr.io/etcd-development/etcd:v3.4.13 \ /usr/local/bin/etcd \ --name s1 \ --data-dir /etcd-data \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://0.0.0.0:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://0.0.0.0:2380 \ --initial-cluster s1=http://0.0.0.0:2380 \ --initial-cluster-token tkn \ --initial-cluster-state new \ --log-level info \ --logger zap \ --log-outputs stderr
動作確認
CoreDNS
と etcd
のコンテナが両方とも起動して、DNS サーバとして動作するか確認します。
まずはコンテナが起動しているか。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8064ae23c30c gcr.io/etcd-development/etcd:v3.4.13 "/usr/local/bin/etcd…" 2 hours ago Up 2 hours 0.0.0.0:2379-2380->2379-2380/tcp etcd e0bce2b55549 coredns/coredns:1.8.0 "/coredns -conf /roo…" 17 hours ago Up 17 hours 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp coredns
コンテナ起動していたら、「etcd へ DNS レコードを登録できるか」「登録した DNS レコードを参照できるか」を確認します。
# etcd への DNS レコード登録 (OK が返ってくれば成功) $ docker exec -it etcd etcdctl put /skydns/com/example/hoge '{"host":"192.168.1.254","ttl":3600}' OK # DNS レコード問い合わせ (上記で設定したアドレスが返ってくれば成功) $ dig +short hoge.example.com @localhost 192.168.2.254
無事に etcd を使った DNS サービスが動作しているようです。
次に、/etc/hosts が参照できるか確認します。
# /etc/hosts へレコード追加 $ sudo sh -c "echo '192.168.1.253 fuga.example.com' >> /etc/hosts" # DNS レコード問い合わせ (上記で設定したアドレスが返ってくれば成功) $ dig +short fuga.example.com @localhost
こちらは期待した結果が返ってきませんでした。
どうやら /etc/hosts
の編集内容がコンテナのほうに同期されていないようです。
理由は分かっていないのですが、コンテナを再起動することで反映されました。
$ docker restart coredns coredns $ dig +short fuga.example.com @localhost 192.168.1.253
これで DNS サーバの構築が完了しました。
MetalLB
MetalLB とは、AKS や GKE などのマネージド k8s と同じようにベアメタル k8s クラスタでも type: LoadBalancer
の Service リソースを使えるようにしてくれるロードバランサーの実装です。
k8s クラスタに MetalLB をデプロイすることで、type: LoadBalancer
のリソースに対して自動的に外部 IP アドレスを払い出してくれます。
今回は MetalLB のデプロイ方法は省略します。
ExternalDNS
いよいよ本命の ExternalDNS です。 ExternalDNS を利用することで、Kubernetes の Service や Ingress で公開される IP アドレスを Kubernetes クラスタ外部の DNS プロバイダに自動登録して、FQDN で Kubernetes クラスタ内のサービスにアクセスできるようになります。
執筆時点の v0.7.3
で選択できる DNS プロバイダ は 27 個あります。
Azure DNS や Route 53 など、対応している DNS プロバイダは他にもあるのですが、オンプレに構築していみたいという目的から CoreDNS を選択しました。
デプロイ
DNS プロバイダ毎にチュートリアルが用意されています。 CoreDNS 用のチュートリアルはこちら。
CoreDNS と etcd は既に構築しているため ExternalDNS のデプロイのみ参照しました。 以下のマニフェストをデプロイします。
--- apiVersion: rbac.authorization.k8s.io/v1beta1 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"] - apiGroups: [""] resources: ["endpoints"] verbs: ["get","watch","list"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 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: v1 kind: ServiceAccount metadata: name: external-dns namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns namespace: kube-system spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.7.3 args: - --source=service - --provider=coredns - --log-level=debug env: - name: ETCD_URLS value: http://192.168.2.3:2379
これにより、namespace: kube-system
で ExternalDNS の Pod が動くようになりました。
アノテーション付与
ExternalDNS で対象とする Service リソースにアノテーションを付与します。 これにより、ExternalDNS がアノテーション内のホスト名と Service が持つ 外部 IP アドレスを CoreDNS へ登録してくれるようになります。
$ kubectl annotate svc <svc_name> "external-dns.alpha.kubernetes.io/hostname=<svc_name>.example.com." service/<svc_name> annotated
動作確認
DNS サーバ構築時と同じく dig で確認します。 サンプルで動かしている WordPress と独自ドメインを対象にしてみます。
# Service 確認 $ kubectl get svc -n wordpress NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress LoadBalancer 10.110.128.227 192.168.2.247 80:30446/TCP 56d wordpress-mysql ClusterIP None <none> 3306/TCP 56d # アノテーション付与 $ kubectl annotate svc wordpress "external-dns.alpha.kubernetes.io/hostname=wordpress.nnstt1.work." -n wordpress service/wordpress annotated # dig $ dig +short wordpress.nnstt1.work 192.168.2.247
無事に外部 IP アドレスを参照することができました。 キャプチャは無いのですが、別マシンのブラウザから WordPress を表示することもできました。
これで、IP アドレスを使わずに FQDN で k8s クラスタのサービスにアクセスすることができるようになりました。
おわりに
以上が k8s クラスタのサービスに FQDN で繋がるようにした手順です。
ExternalDNS を使うことがメインの目的だったのですが、CoreDNS と etcd を触る機会も得られました。
ExternalDNS も思ってたより簡単に導入できたので、同じような悩みを持っている方はぜひ検討してみてください。
アノテーション付与を手動でする必要がある、という課題があるので、今後は自動的にアノテーションを付与する仕組みを調べたいと思います。
ArgoCD で Kiali Operatr アプリケーションを削除できない場合の対処
ArgoCD に作成した KIali Operator アプリケーションが削除できなくなってしまった問題に対処しました。
再現
Kiali Operator アプリケーション作成
ArgoCD に Kiali Operator のアプリケーションを作成します。
作成できました。
アプリケーション削除
ArgoCD から先ほど作成した Kiali Operator アプリケーションを削除します。
下図のように Deleting のまま残ってしまいました(環境に依るかもしれません)。
対処
リソースの確認
Kiali Operator で作成されたリソースが残っていないか確認します。
$ kubectl get all -n kiali-operator No resources found in kiali-operator namespace. $ kubectl get all -n istio-system NAME READY STATUS RESTARTS AGE pod/istio-egressgateway-b9d46896-mb6np 1/1 Running 0 6d5h pod/istio-ingressgateway-dc76747bf-vk9g5 1/1 Running 0 6d5h pod/istiod-69c88fcb8-d25rl 1/1 Running 0 6d5h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istio-egressgateway ClusterIP 10.109.88.14 <none> 80/TCP,443/TCP,15443/TCP 9d service/istio-ingressgateway LoadBalancer 10.105.166.93 192.168.2.245 15021:30988/TCP,80:31835/TCP,443:31962/TCP,31400:31141/TCP,15443:32537/TCP 9d service/istiod ClusterIP 10.108.38.61 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP,853/TCP 9d NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/istio-egressgateway 1/1 1 1 9d deployment.apps/istio-ingressgateway 1/1 1 1 9d deployment.apps/istiod 1/1 1 1 9d NAME DESIRED CURRENT READY AGE replicaset.apps/istio-egressgateway-b9d46896 1 1 1 9d replicaset.apps/istio-ingressgateway-dc76747bf 1 1 1 9d replicaset.apps/istiod-69c88fcb8 1 1 1 9d $ kubectl get crd NAME CREATED AT ...(snip)... kialis.kiali.io 2020-09-17T21:34:39Z ...(snip)...
Pods や Services は無くなっていますが、CRD (Custom Resource Definition) kialis.kiali.io
が残ってしまっています。
$ kubectl describe crd kialis.kiali.io Name: kialis.kiali.io Namespace: Labels: <none> Annotations: <none> API Version: apiextensions.k8s.io/v1 Kind: CustomResourceDefinition Metadata: Creation Timestamp: 2020-09-17T21:34:39Z Generation: 1 Managed Fields: API Version: apiextensions.k8s.io/v1beta1 Fields Type: FieldsV1 fieldsV1: f:metadata: f:annotations: .: f:kubectl.kubernetes.io/last-applied-configuration: f:spec: f:conversion: .: f:strategy: f:group: f:names: f:kind: f:listKind: f:plural: f:singular: f:preserveUnknownFields: f:scope: f:subresources: .: f:status: f:version: f:versions: f:status: f:storedVersions: Manager: argocd-application-controller Operation: Update Time: 2020-09-17T21:34:39Z API Version: apiextensions.k8s.io/v1 Fields Type: FieldsV1 fieldsV1: f:status: f:acceptedNames: f:kind: f:listKind: f:plural: f:singular: f:conditions: Manager: kube-apiserver Operation: Update Time: 2020-09-17T21:34:39Z Resource Version: 4465211 Self Link: /apis/apiextensions.k8s.io/v1/customresourcedefinitions/kialis.kiali.io UID: a36f4355-483b-4a20-92cb-37a2f5a91902 Spec: Conversion: Strategy: None Group: kiali.io Names: Kind: Kiali List Kind: KialiList Plural: kialis Singular: kiali Preserve Unknown Fields: true Scope: Namespaced Versions: Name: v1alpha1 Served: true Storage: true Subresources: Status: Status: Accepted Names: Kind: Kiali List Kind: KialiList Plural: kialis Singular: kiali Conditions: Last Transition Time: 2020-09-17T21:34:39Z Message: no conflicts found Reason: NoConflicts Status: True Type: NamesAccepted Last Transition Time: 2020-09-17T21:34:39Z Message: the initial names have been accepted Reason: InitialNamesAccepted Status: True Type: Established Stored Versions: v1alpha1 Events: <none>
CRD 削除
残ってしまった CRD kialis.kiali.io
を削除します。
$ kubectl delete crd kialis.kiali.io customresourcedefinition.apiextensions.k8s.io "kialis.kiali.io" deleted
返ってきません…。 ArgoCD も依然として Deleting です。
CRD の状態を見てみます。
$ kubectl describe crd kialis.kiali.io Name: kialis.kiali.io Namespace: Labels: <none> Annotations: <none> API Version: apiextensions.k8s.io/v1 Kind: CustomResourceDefinition Metadata: Creation Timestamp: 2020-09-17T21:34:39Z Deletion Timestamp: 2020-09-18T21:28:07Z Finalizers: customresourcecleanup.apiextensions.k8s.io Generation: 1 ...(snip)... Conditions: Last Transition Time: 2020-09-17T21:34:39Z Message: no conflicts found Reason: NoConflicts Status: True Type: NamesAccepted Last Transition Time: 2020-09-17T21:34:39Z Message: the initial names have been accepted Reason: InitialNamesAccepted Status: True Type: Established Last Transition Time: 2020-09-18T21:28:07Z Message: CustomResource deletion is in progress Reason: InstanceDeletionInProgress Status: True Type: Terminating Stored Versions: v1alpha1 Events: <none>
Metadata に Finalizers customresourcecleanup.apiextensions.k8s.io
が追加されています。
また、Conditions を見ると、削除処理は実行中のようです。
この状態で CRD が残り続けてしまったので、強制的に削除します。
$ kubectl patch crd kialis.kiali.io -p '{"metadata":{"finalizers":[]}}' --type=merge customresourcedefinition.apiextensions.k8s.io/kialis.kiali.io patched $ kubectl get crd kialis.kiali.io Error from server (NotFound): customresourcedefinitions.apiextensions.k8s.io "kialis.kiali.io" not found
CRD を削除できたようです。
このタイミングで、ArgoCD に残り続けていた Kiali Operator アプリケーションも削除完了しました。
おわりに
無事に Kiali Operator アプリケーションを削除することができました。
しかし、なぜ CRD が残り続けてしまったかを把握できていません。
Kubernetes は奥が深いようなのでじっくり勉強していきます。
参考にしたリンク
Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
朝活_20200917
ArgoCD 動かなくて 1 週間停滞してしまいました。
やったこと
ArgoCD にアプリケーション作成
まずは k8s クラスタの土台となる manifests 群を GitHub から Pull できるように、ArgoCD にアプリケーション作成。
- MetalLB
- Rook-ceph
- Prometheus
Istio はインストール方法が上記と異なるので未対応。 Helm からインストールできる?(未確認)
またサンプルアプリケーションとして、WordPress と Istio の guestbook-ui
をデプロイ。
Kiali インストール
Istio で構築したサービスメッシュを観るため Kiali
を導入。
Helm から Kiali Operator を入れられるということで ArgoCD 経由で入れようとしたが、うまくデプロイできなかったので削除。
しかし延々と Deleting 状態…。
Grafana のデータ永続化
kube-prometheus
についてきた Grafana は ストレージとして emptyDir
使っていたので、Pod 削除すると認証情報とかが吹き飛ぶ。
Rook-ceph で用意した storageclass
を指定した persistentVolumeClaim
を作成して、Grafana のデータを永続化。
わかったこと
ArgoCD で GitOps を簡単に体験できる
git に登録した manifests を変更したら、自動的に k8s クラスタの状態も変更される。 これはとても便利!
Istio 入れただけではサービスメッシュの観測はできない
Kiali
や Jaeger
を使う必要があるみたい。
自宅ラボでの目標が不明瞭
とりあえず自宅ラボに k8s クラスタ立てて触ってるけど、何をしたいのか分からなくなっている。
明確な目標(指標)立てたほうが良いのでは。
次にやること
- Grafana 以外のデータ永続化
- ArgoCD から Kiali 消せない問題の解決
- 自宅ラボの目標決め
朝活_20200910
やったこと
# ceph dashboard $ kubectl apply -f rook/cluster/examples/kubernetes/ceph/dashboard-loadbalancer.yaml $ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo
MetalLB で払い出された Exernal IP にブラウザでアクセス
[f:id:nnstt1:20200910063407p:plain]
- ArgoCD CLI ツール インストール
# argocd cli $ VERSION=$(curl --silent "https://api.github.com/repos/argoproj/argo-cd/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') $ sudo curl -SL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/$VERSION/argocd-linux-amd64 $ sudo chmod +x /usr/local/bin/argocd </code></pre> admin パスワード変更 <pre style="background-color: rgb(40, 44, 52) !important;"><code class="bash"># change admin pass $ kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o name | cut -d'/' -f 2 $ argocd login <Exernal IP> $ argocd account update-password
Web Console ログイン
[f:id:nnstt1:20200910064509p:plain]
サンプルアプリ デプロイ
[f:id:nnstt1:20200910065126p:plain]
[f:id:nnstt1:20200910065153p:plain]
[f:id:nnstt1:20200910065216p:plain]
うまく動かず…
[f:id:nnstt1:20200910065430p:plain]
- Istio インストール
$ curl -L https://istio.io/downloadIstio | sh - $ cd istio-1.7.0 $ export PATH="$PATH:/home/nnstt1/istio/istio-1.7.0/bin" $ istioctl x precheck $ istioctl install --set profile=demo $ kubectl label namespace default istio-injection=enabled
サンプルアプリ デプロイ
# sample $ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml $ kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -s productpage:9080/productpage | grep -o "<title>.*</title>" $ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml $ istioctl analyze Warn [IST0103] (Pod busybox-sleep-less.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod busybox-sleep.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod wordpress-7b989dbf57-6956z.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod wordpress-mysql-6965fc8cc8-k76t4.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Info [IST0118] (Service wordpress.default) Port name (port: 80, targetPort: 80) doesn't follow the naming convention of Istio port. Error: Analyzers found issues when analyzing namespace: default. See https://istio.io/docs/reference/config/analysis for more information about causes and resolutions.
わかったこと
- ArgoCD がそのままでは動かない
kubectl logs -f -n argocd argocd-server-xxxx
でログを見ても、エラー内容をよくわかっていない
- Istio の概要はわかっていても使い方を把握できていない
次にやること
- ArgoCD サンプルアプリを動くようにする
やり残し
- Grafana Dashboard インポートできない事象の調査
- Istio の使い方を調べる
参照したリンク
Getting Started - Argo CD - Declarative GitOps CD for Kubernetes
追記
今日の記録を Twitter に流したところ、@zaki_hmkc さんから Istio のことについて教えていただきました!ありがとうございます!
namespaceにistio-injection=enabledラベルをセットすると「対象namespaceで動く全てのpodをIstioで動作させる」ことになるので、wordpress(Rookのサンプルかな?)とは分けた方がよいかも (もちろんこの子をIstioで動作させるってのを試してみてもよいですがw)
— z a k i (@zaki_hmkc) September 9, 2020
analyzeで警告が出てるのがこの件(injection=enabledになってるのにIstio proxyがインジェクションされてない)なので、逆に言えばwordpressとbusyboxのpodはrestartしてあげればIstioのサイドカーがセットされると思います!
— z a k i (@zaki_hmkc) September 9, 2020
(ラベルセット前にデプロイされていたpodなので、サイドカーが入ってない)
Pod 削除前
Istio 入れる前から動かしていた namespace: default の Pod たちで Warn が出ています。
$ kubectl get pods NAME READY STATUS RESTARTS AGE busybox-sleep 1/1 Running 0 26h busybox-sleep-less 1/1 Running 94 26h details-v1-79c697d759-hb47t 2/2 Running 0 87m productpage-v1-65576bb7bf-lr2tx 2/2 Running 0 87m ratings-v1-7d99676f7f-nd8hr 2/2 Running 0 87m reviews-v1-987d495c-w7png 2/2 Running 0 87m reviews-v2-6c5bf657cf-98dnp 2/2 Running 0 87m reviews-v3-5f7b9f4f77-tr4j9 2/2 Running 0 87m wordpress-7b989dbf57-6956z 1/1 Running 0 3d1h wordpress-mysql-6965fc8cc8-k76t4 1/1 Running 0 3d1h $ istioctl analyze Warn [IST0103] (Pod busybox-sleep-less.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod busybox-sleep.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod wordpress-7b989dbf57-6956z.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod wordpress-mysql-6965fc8cc8-k76t4.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Info [IST0118] (Service wordpress.default) Port name (port: 80, targetPort: 80) doesn't follow the naming convention of Istio port. Error: Analyzers found issues when analyzing namespace: default. See https://istio.io/docs/reference/config/analysis for more information about causes and resolutions.
WordPress Pod 削除
Rook のサンプルとして使っていた WordPress Pod を削除してみます。
$ kubectl delete pods wordpress-7b989dbf57-6956z pod "wordpress-7b989dbf57-6956z" deleted $ kubectl get pods NAME READY STATUS RESTARTS AGE busybox-sleep 1/1 Running 0 26h busybox-sleep-less 1/1 Running 94 26h details-v1-79c697d759-hb47t 2/2 Running 0 87m productpage-v1-65576bb7bf-lr2tx 2/2 Running 0 87m ratings-v1-7d99676f7f-nd8hr 2/2 Running 0 87m reviews-v1-987d495c-w7png 2/2 Running 0 87m reviews-v2-6c5bf657cf-98dnp 2/2 Running 0 87m reviews-v3-5f7b9f4f77-tr4j9 2/2 Running 0 87m wordpress-7b989dbf57-rwwqb 2/2 Running 0 16s <- READY が 2/2 になった(削除前は 1/1) wordpress-mysql-6965fc8cc8-k76t4 1/1 Running 0 3d1h $ istioctl analyze Warn [IST0103] (Pod busybox-sleep-less.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod busybox-sleep.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. Warn [IST0103] (Pod wordpress-mysql-6965fc8cc8-k76t4.default) The pod is missing the Istio proxy. This can often be resolved by restarting or redeploying the workload. <- wordpress-7b989dbf57-6956z の Warn がなくなった Info [IST0118] (Service wordpress.default) Port name (port: 80, targetPort: 80) doesn't follow the naming convention of Istio port. Error: Analyzers found issues when analyzing namespace: default. See https://istio.io/docs/reference/config/analysis for more information about causes and resolutions.
サイドカーとして istio-proxy
コンテナが WordPress Pod 内で立ち上がったようです。
Pod 全削除
Warn となっていた他の Pod も削除してみました。 busybox のPod は Deployment で起動したものでは無いため、失くなってしまいました。 (restart ではなく delete してしまいました。。)
$ kubectl delete pods busybox-sleep busybox-sleep-less wordpress-mysql-6965fc8cc8-k76t4 pod "busybox-sleep" deleted pod "busybox-sleep-less" deleted pod "wordpress-mysql-6965fc8cc8-k76t4" deleted $ kubectl get pods NAME READY STATUS RESTARTS AGE details-v1-79c697d759-hb47t 2/2 Running 0 102m productpage-v1-65576bb7bf-lr2tx 2/2 Running 0 102m ratings-v1-7d99676f7f-nd8hr 2/2 Running 0 102m reviews-v1-987d495c-w7png 2/2 Running 0 102m reviews-v2-6c5bf657cf-98dnp 2/2 Running 0 102m reviews-v3-5f7b9f4f77-tr4j9 2/2 Running 0 102m wordpress-7b989dbf57-rwwqb 2/2 Running 0 14m wordpress-mysql-6965fc8cc8-t7s92 2/2 Running 0 12m <- READY が 2/2 になった(削除前は 1/1) $ istioctl analyze Info [IST0118] (Service wordpress.default) Port name (port: 80, targetPort: 80) doesn't follow the naming convention of Istio port.
無事に Warn は出なくなりました。
Info メッセージが出ていますが、時間がないのはこれはまた後日確認します。。