koukiblog

たぶんweb系の話題

Kubernetes The Hard Wayをやった

前からずっとやろうと思った Kuberentes The Hard Way( GitHub - kelseyhightower/kubernetes-the-hard-way: Bootstrap Kubernetes the hard way on Google Cloud Platform. No scripts. ) をやっとやりました。

GCPアカウントがあったのでそこで進めたのですが、クラウドシェルで作業すべて完結できました。 GCPアカウントを新規作成していない場合は、無料枠内で出来ると思います。

途中詰まったところ

  • HOSTNAMEが取得できない

kubernetes-the-hard-way/09-bootstrapping-kubernetes-workers.md at master · kelseyhightower/kubernetes-the-hard-way · GitHub

9章で利用しているHOSTNAMEの取得方法が載っていなくてちょっと迷いました

HOSTNAME=$(curl -H Metadata-Flavor:Google http://metadata/computeMetadata/v1/instance/hostname | cut -d. -f1) で解決。

参考: google cloud platform - GCE metadata - get instance name - Server Fault

  • core-dns pod がずっとpending

手順をすべて終えたあとの動作確認で、core-dns podを作成するのですが、そこで作成したpodがずっとpendingのままという問題が起きました。Kubernetesマスターノードの運用はやったことないので、どのログ見ればいいのかもわからない状態で結構悩みました。

podをNodeに配置するところで止まってそうなので、kube-schedulerかなと思ってkube-schedulerのログを見ると、作業ミスで設定ファイルがなくて起動失敗していて、それが原因でした。

感想

GKEのようなマネージドサービスを使った運用しかしていなかったので、Masterノードがどういうコンポーネントで構成されているか少しでも体験できたのはよかったです。特にkube-schedulerの起動ミスのようなトラブルシューティングを経験できたのはよい経験になりました。

あとGCPクラウドシェルが思ってたより高機能で便利。Tmuxも使えました。普段の開発ももしかしたらクラウドシェル上で出来るかも。

Nginx Ingress Controller v1.0 でIngress リソースが作れなくなって困った話

Nginx Ingress Controllerをv0.3からV1.0系にアップデートしたのだけど、そのときにIngressリソース作成時にエラーが出るようになってはまったのでメモ。

エラーはこういうエラーが出ました

Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": Post https://ingress-nginx-controller-admission.ingress-nginx.svc:443/extensions/v1beta1/ingresses?timeout=30s: context deadline exceeded

同じエラーのIssueがありました。 github.com

Issue内にも記載がある通り、MasterノードからWorkerノードのポート:8443でのアクセスを許可すれば解決しました。(なぜかIssueは解決していなくて、0.3系にバージョンダウンしたりAdmission Webhookを無効にしたりして解決してる方もいる様子)

この問題は、GKEのプライベートクラスタなど、MasterノードからWorkerノードへのアクセスを制限しているのが原因のようです。僕が検証していた環境はGKEのプライベートクラスタだったので、ファイアウォールルールの追加が必要でした。Issueをみると様々な環境でこの問題が発生してるようなので、もしかすると他の要因もあるかもしれないですが・・

NginxIngressControllerとGKEどちらのドキュメントにも記載がありました。

For private clusters, you will need to either add an additional firewall rule that allows master nodes access to port 8443/tcp on worker nodes, or change the existing rule that allows access to ports 80/tcp, 443/tcp and 10254/tcp to also allow access to port 8443/tcp.

See the GKE documentation on adding rules and the Kubernetes issue for more detail. Installation Guide - NGINX Ingress Controller

  • GKE
このセクションでは、限定公開クラスタにファイアウォール ルールを追加する方法について説明します。デフォルトでは、クラスタ コントロール プレーンはポート 443(HTTPS)および 10250(kubelet)上のみでノードおよび Pod への TCP 接続を開始するようにファイアウォール ルールによって制限されています。一部の Kubernetes 機能では、他のポート上でアクセスを許可するためにファイアウォール ルールを追加する必要があります。

追加のファイアウォール ルールを必要とする Kubernetes 機能は次のとおりです。

アドミッション Webhook
集計 API サーバー
Webhook 変換
動的監査の構成
通常、ServiceReference フィールドを持つ API では、追加のファイアウォール ルールが必要です。

cloud.google.com

Admission Webhookはv1.0から利用されているので、アップデート時にこの問題に直面すると結構はまりやすい気はしました。アプリケーションの動作確認はしてたけど、Ingressリソース作成成功するかのような確認はしてなかったので結構あせりました。

GCPのCloudCDNにはStaleコンテンツという機能がある

GCPのCloudCDN(https://cloud.google.com/cdn) を設定したのですが、staleコンテンツという機能があるのを知らずに結構はまったので残しておきます。

Staleコンテンツ?

Staleコンテンツは、オリジンがエラーを返したときなどある条件を満たすと、CDNから期限切れの古いコンテンツを返す設定です。

cloud.google.com

cdn_cache_statusをカスタムレスポンスヘッダーに設定しておくと、"stale"というステータスになっているのでそれで判別できます。

この機能が意図せず有効になっていて、なぜ古いコンテンツが表示されるのかわからず結構悩みました。

Flux2について調べた

久しぶりにFluxを調べたらFlux2が出ていて、別物になっていたので調べたことをまとめます。

Flux2について

ArgoCDと合流して、GitOpsEngineを開発していくみたいな話になったのですが、結局両者は別れることにことになり、WeaveWorks社が出したのがFlux2 です。 Fluxをカスタムコントローラーなどを利用してリライトしたものという理解でよさそうです。 思想は引き継いでいますが、インストール方法や設定方法はFlux1とは全然違います。

変わったところ

Fluxの各機能はコンポーネント化されていて、カスタムリソースを利用して設定していく形になっています。 また、Helmも正式サポートされています。

Helmを使った例がわかりやすいので紹介すると、今までHelmを利用する場合は下記のようにhelmコマンドを使ってインストールしていたと思います。 helm repo add で chartをレポジトリに追加し、 helm installでchartをクラスタにインストールしています。

helm repo add traefik https://helm.traefik.io/traefik
helm install my-traefik traefik/traefik \
  --version 9.18.2 \
  --namespace traefik

それが、Fluxを使うとこうなります。

flux create source helm traefik --url https://helm.traefik.io/traefik
flux create helmrelease --chart my-traefik \
  --source HelmRepository/traefik \
  --chart-version 9.18.2 \
  --namespace traefik

flux create コマンドはk8sクラスタにリソースを作っているわけではなく、それぞれ、 Source, HelmRelease というFluxが定義したカスタムリソースを作成しています。 そして、そのカスタムリソースをHelm Controllerが参照してChartをクラスタにデプロイします。

Gitレポジトリからマニフェストを取得してApplyする場合は、 source として GitRepositryを定義し、それをapplyするkustomizeリソースを作成します。最初ちょっとわかりづらかったのですが、kubectl applyするだけのリソースはなく、 kustomizeリソースを利用するようです。

先ほどのHelmの例で作ったカスタムリソースは下記のようになります。これをkustomizationリソースで反映しても flux create コマンドと同じことができます。

# /flux/boot/traefik/helmrepo.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
  name: traefik
  namespace: traefik
spec:
  interval: 1m0s
  url: https://helm.traefik.io/traefik
# /flux/boot/traefik/helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: traefik
  namespace: traefik
spec:
  chart:
    spec:
      chart: traefik
      sourceRef:
        kind: HelmRepository
        name: traefik
      version: 9.18.2
  interval: 1m0s

GitOpsツール自体の設定の可視化は課題だと思っていたので、カスタムリソースで定義できるのは非常によさそうです。

Configレポジトリのサンプル

マニフェストを保存するConfigレポジトリのサンプルが提供されていました。これをベースにカスタマイズしていくのがよさそうかなと思います。

github.com

ArgoCD との比較

こちらにまとまっていました。個人的にはArgoCDは権限周りが複雑そうで、k8sのRBACをそのまま使えるFluxが好みです。

ArgoCD と Flux2 の比較

有痛性外脛骨の除去手術を受けた

先日、有痛性外脛骨の除去手術を受けました。 手術前いろいろ調べたのですが、あまり情報がなくて不安になったりしたので、出来るだけ残しておこうと思います。

このブログは手術して退院直後に書いてます。

前提

僕は、冬の間はほぼ毎週スキーに行っています。今回の手術はほぼそのために行いました

経緯

1月にいったスキーの帰りで、スキーブーツを脱ぐときに右足首に違和感と痛みがあったのが始まりです。(冬の間はスキーにほぼ毎週行ってます 最初はブーツのシェルに出っ張りが出来たのかと思ったのですが、よくよく見ると、右足の内くるぶし前方に骨の出っ張りが出来ていました。

気にせずスキーに通い続けていたのですが、症状はどんどん悪化し、ブーツを脱ぐのが激痛。スキー中も痛みがすごくて半日が限界。という状況になってしまいました。特にリフトに乗ってるときの、足が浮いている状態が痛みが激しかったです。 せっかくお金をかけてスキー場に行ってるのにこれではもったいないと思い、整形外科に行ってみることにしました。

初回の通院にて、外脛骨障害という診断。CTをとったところ、僕の場合は大きく剥離(?)してしまっているので、自然治癒の可能性はほぼなし、手術で摘出がおすすめ、という話になりました。外脛骨とは、足にある余剰骨のことで、偏平足や色々な要因で内くるぶしに前方に飛び出してしまうことがあり、それに痛みが出ると有痛性外脛骨障害、ということらしいです。

ただ手術してしまうと、数か月はリハビリが必要ということで、手術がシーズンが終わったあとに行うことにしました。

手術を受けるために病院へ

4月後半に病院に向かい、手術を受ける意向を伝えたところ、さくっと2週間後に決定しました。 入院は2泊3日で、全身麻酔を使った手術を受けることがここで決まりました。

1日目: 手術前の検査

2日目: 手術

3日目: 退院

というスケジュールです。

全身麻酔を行う場合は、前日夜から絶食を行う必要があるため、これが最短スケジュールになります。

入院・手術

入院当日は手術前に必要な検査をしただけで、半日ほぼやることなし。入院するときは、時間つぶせる本を持ち込むのがおすすめです。翌日の手術の開始時間もこの日に教えてもらえました。

翌日朝起きると、手術の開始時間を伝えられます。手術の開始時間が近づくと、自分のベッドで手術着に着替えて待ちます。時間になると、手術室までは自分で歩いて行きます。ベッドで運ばれるイメージだったので意外でした。

手術室に入ると、担当の看護師や麻酔医の方から挨拶されます。手術室の奥に、手術台があり、自分で手術台に横になります。いくつか確認があったあと、全身麻酔が始まります。全身麻酔は初めてだったのですが、麻酔が始まった瞬間に頭が重くなる感じがあり、次に目が覚めると手術はもう終わっていました。時計を見ると1時間半ほどかかっていたのですが、体感は数秒でした。ただ、目覚めた直後は少し息苦しい感じと、悪寒、震えはありました。しばらくすると正常になりました。

手術した箇所は鎮痛剤を点滴されているのであまり痛みは感じないものの、じんわり痛みがあり、それを見て初めて手術した実感がわきました。 手術した当日は、右足に体重をかけてはいけない(そもそも痛くてかけられないけど)ので、移動は車いす、ベッドからの移動はナースコールで看護師さんを呼んで移動することになります。自分の意思で移動できないのはかなりストレスありました。

リハビリ・退院

翌日朝、リハビリとして、松葉杖の使い方を教えてもらい、問題なく移動できるようになれば、退院です。

余談ですが、松葉杖を使った階段昇降はかなり恐怖ありました。(上る場合は、松葉杖で全体重ささえて、片足ジャンプすることになる) 出来なくて入院延長もよくある話だそうです。

家の中を松葉杖で移動するのも、結構を気を使います。少なくとも松葉杖で移動できる導線を確保してもらうところまでは、家族などに協力してもらわないとなかなか厳しいです。移動中常に両手がふさがるので、何かを持って移動する、ということが基本的に出来ません。

仕事については、コロナの影響もあり、自宅で仕事できるので影響はありませんでしたが、外に出る仕事はかなり難しいと思います。

手術前にしばらく松葉杖ということは聞いていたのですが、思ってたより大変でした。

費用について

病院や、手術内容によって大きく変わると思うのであまり参考にならないとは思うのですが、入院は2泊3日、全身麻酔で手術時間2時間ほどで、6万5千円ぐらいでした。 限度額適用認定証を一応取り寄せていたのですが、結果的には不要でした。

スキー旅行一回我慢するぐらいの金額で出来るので、もし同じように痛みに我慢してスキーしてる方がいたら手術してしまうのがよいかもしれません。

その後

一週間後に抜糸、その後リハビリです。元のように動ける日が待ち遠しいです。スキーブーツの痛みもなくなってるといいな。(痛みの原因である骨がなくなったのでたぶん大丈夫だとは思うんですが

Nginx Ingress ControllerはAnnotationに不正な値をセットすると503を返す

起きたこと

Nginx Ingress Controllerが突然503しか返さなくなり、原因を調べるのにめちゃくちゃ時間がかかってしまった。

前提

Nginx Ingress Controllerは、ユーザーの記述したManifestを元にNginxのconfを生成します。これは、Nginx Ingress ControllerのPodにログインして、 /etc/nginx/nginx.conf を参照すると確認できます。

原因

Nginx Ingress Controllerのソースコードを確認すると、confを生成しているテンプレートがあり、503を返す箇所を確認することができます。なぜエラーなのかもコメントとして出力されます。

ingress-nginx/nginx.tmpl at ff74d0ff3316a1446ffc73ae0e7a05a8a252551f · kubernetes/ingress-nginx · GitHub

巨大なif文なので、ちょっと読みづらいですが、

            {{ else }}
            # Location denied. Reason: {{ $location.Denied | quote }}
            return 503;
            {{ end }}

に対応するif文は、1069行目の

{{ if isLocationAllowed $location }}

です。

ingress-nginx/nginx.tmpl at ff74d0ff3316a1446ffc73ae0e7a05a8a252551f · kubernetes/ingress-nginx · GitHub

たとえば、White List SourceRange のようにCIDRを期待しているannotationに、CIDRではない値をセットすること、 isLocationAllowdがFalseになり、503を返します。

Annotationの一覧はこちら

kubernetes.github.io

学び

Nginx Ingress Controllerをある種ブラックボックスとして扱っていたのですが、Nginx Ingress Controllerはnginxのconfを生成しているということを知っていれば、もっと早く解決できたので、利用しているミドルウェアが何をしているのかはだいたいでいいので把握するようにしておきたいと思いました。とはいえ、まさかNginxのconfにエラーメッセージがあるとは思い至らないのでなかなか難しいところですが。

Nginx Ingress Controller + oauth2-proxyでSSOしてるときに、特定のIPアドレスからのリクエストのみ認証をスキップさせる

タイトルに書いてある通りのことがやりたかったのだけど、結構はまったので残しておきます。 脆弱性テストのためのクローラを通すとか、必要なシチュエーションはあるのではないでしょうか。

色々試行錯誤したのですが、下記の手順でいけました。

Nginx Ingress Controllerのfowarded-forオプションを設定する

k8sクラスタの前段にCDNなどを配置している場合はまずこれが必要です。

use-forwarded-headersにTrueを設定することで、Nginx Ingress Controllerからプロキシされる先にX-Forwaded系のヘッダーがセットされます。

ConfigMap - NGINX Ingress Controller

oauth2-proxyの設定を追加する

oauth2-proxyでX-Forwaded-ForヘッダにセットされたIPを受け取るには、reverse-proxyオプションと、real-client-ip-headerオプションを指定する必要があります。(oauth2-proxyのデフォルトはX-Real-IPのため、real-client-ip-headerオプションで設定する必要があります) その上で、trusted-ipオプションを指定します。

また、気付きづらいのですが、 trusted-ipを指定し、認証がスキップされると、oauth2-proxyはそのままupstreamにリクエストをプロキシします。そのため、upstreamが指定されていないと、404を返却し、Nginx Ingress Controllerが行う認証が失敗してしまいます。この場合、Nginx Ingress Controllerは500を返します。これを回避するためには、 20xを返す upstreamを設定してあげればよいです。 (static://202 など)

ここまでのオプションをまとめるとこうなります。

--real-client-ip-header=X-Forwarded-For
--reverse-proxy=true
--upstream=static://202
--trusted-ip=xxx.xxx.xxx.xxx
--trusted-ip=yyy.yyy.yyy.yyy
--trusted-ip=zzz.zzz.zzz.zzz

参考

Nginx Ingress Controller とoauth2-proxyを利用したSSOについて blog.1q77.com

trusted_ipの挙動について github.com