k8s(k3s)クラスタでLet's Encrypt自動更新のIngressを構築する

k8s(k3s)クラスタでLet's Encrypt自動更新のIngressを構築する

author potpro(ぼとぷろ)
2019/11/21

k8s(k3s)クラスタでLet's Encrypt自動更新のIngressを構築する

サブタイトル:ぼくのかんがえた最強のk3sクラスタ

前回の記事がかなり読まれたようで何よりなので、せっかくなので次のステップに進む記事をちゃんと書きます。

とはいえ前回と違ってk8sのコアな内容になってくるので、難しいかもしれません。

後、前回の記事の続きですので、まず前回の記事を先に見たほうがいいです。

前回の記事はこちら↓

Oracle Cloudの無料枠だけでKubernetes(k3s)クラスタを構築する

Ingress

タイトルは何やらよくわからないことになっていますが、

今回構築する基盤はkubernetesでHTTP(S)ロードバランサーの役割を果たす、Ingressの機能を使用してL7ロードバランサーを構築します。

前回の記事では、NodePortを使って外部公開していました。

しかしこれだとPortごとに1つしか外部公開できません。複数のポートを設定してOCIのロードバランサーを使うことになってしまいます。あまりスマートではない。

またhttpsでのアクセス自体をk8sで対応出来ていません。最近のWebサービスはhttpsがデフォだと思っているのでほしいところ。 なのでうちのブログでもβの頃から使っていたけど今やかなり有名となったフリーな SSL/TLS 証明書のLet's Encryptを使用してhttpsでサービス公開を行いたいと思います。

この問題の解消としてIngressを使うことにより、k8s内部で複数のサービスに振り分けられるL7ロードバランサー機能を持たせることができ、 機能を併用することでLet's EncryptのACMAプロトコル認証により自動でサービスのhttps化もしてくれる、SaaS環境のような基盤へと進化します。

また、公開するサービスはingressを必ず経由することになりますので、一元管理にもなるため、Ingressを使って公開することはk8s自体推奨される方法となっています。

詳しくは、k8sのIngressのリファレンスがわかりやすいです。(日本語が無いのが厳しいけど・・・

Ingress - Kubernetes

Ingress Controller

k8sのingressはただの機能の名称で、実装自体は含まれてません。そのためingress機能を使用するにはingressをサポートしたリバースプロキシのソフトウェア(またはハードウェア)を使う必要があります。これを実装したソフトウェアをIngress Controllerというらしいです。

Ingress Controllers - Kubernetes

マネージドk8sなどであればこのIngress Controllerは最初からそのクラウドのロードバランサーを使用した実装が大体最初からされていて、簡単に使えるようになっています。

されていない場合は自分で組み込んで使用する必要があります。その中でもNGINX Ingress Controllerが有名で記事も結構多いです。やはりnginxが一番メジャーで、信頼性も高いですからね・・・。

しかし今回はk3sがデフォルトで採用されているtraefikを使用しています。

Traefik

tre

traefikはGo言語で書かれたコンテナサービス向けのリバースプロキシのOSSです。

前回の記事でk3s Serverを起動しましたが、実はtraefikはk3sのServerを起動した時点で起動しています。 これにより最初からingressがk3sで使えるようになっています。

> kubectl get pod --namespace kube-system | grep traefik
svclb-traefik-cv4kr                       3/3     Running     0          13d
svclb-traefik-t7bc4                       3/3     Running     0          13d
helm-install-traefik-f8l5b                0/1     Completed   27         13d
traefik-65bccdc4bd-prd6b                  1/1     Running     0          4d10h

触った感じはGo言語製で設定もシンプルでやりやすそうです。

treafikはlegoを使用しLet's EncryptのACMAプロトコルを使ったTLS証明書自動取得機能と3か月ごとの自動更新を備えています。

これにより、Ingressを使用した通信は自動的にLet's Encryptの証明書を使ったhttpsアクセスが可能となります。

Let's Encrypt - Traefik

NGINX Ingress Controllerを使う場合は、nginxに取得する機能が無いため、別のアドオンであるcert-manager等を使用する必要があるのですが、こっちはその必要が無くシンプルです。

helm charts

k3sには勝手にtreafikが起動されているといいましたが、どうやって起動しているのか。ドキュメントを見ると設定ファイルはどうやらここにあるようです。

Traefikは、サーバーの起動時にデフォルトでデプロイされます。詳細については、「マニフェストの自動展開」を参照してください。デフォルトの設定ファイルは/var/lib/rancher/k3s/server/manifests/traefik.yamlにあり、このファイルに加えられた変更はkubectl applyと同様の方法でKubernetesに自動的にデプロイされます。 Traefik Ingress Controllerは、ホスト上のポート80、443、および8080を使用します(つまり、これらはHostPortまたはNodePortには使用できません)。 traefik.yamlファイルでオプションを設定することにより、ニーズに合わせてtraefikを微調整できます。詳細については、公式のTraemik for Helm Configuration Parametersのreadmeを参照してください。

Networking - k3s Document

そしてまたよくわからないものが出てきましたが、この設定ファイルはhelm charts向けのものになっています。

helmはk8sのパッケージ管理ツールで、chartsはその設定ファイルのメタデータに当たります。

つまりkubeの設定ファイルの設定ファイルなわけで、chartsのドキュメントを見ながらtreafitの設定を変更していきます。

(正直ここまでで覚えることが多過ぎて辛い)

treafikのhelm chartsのドキュメントはここにあります。設定していくだけなのでこれを見るとまあそこまで難しくないです。設定項目めっちゃ多いけど。

Traefik Ingress Controllerの設定

ここでやることは、

Traefik Ingress Controllerは、ホスト上のポート80、443、および8080を使用します(つまり、これらはHostPortまたはNodePortには使用できません)。

と書いてあるようにデフォルトのService設定はtype:LoadBalancerでホストからしか繋がらない問題の解消のため、

NodePortを使用してtreafikに渡すための設定変更と、traefikの動作でLet's Encryptを使用する設定を記述します。

/var/lib/rancher/k3s/server/manifests/traefik.yamlをコピーして値を変更します。

apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: traefik
  namespace: kube-system
spec:
  chart: https://%{KUBERNETES_API}%/static/charts/traefik-1.77.1.tgz
  valuesContent: |-
    service:
      nodePorts:
        https: 30000
    serviceType: "NodePort"
    rbac:
      enabled: true
    ssl:
      enabled: true
      enforced: true
    metrics:
      prometheus:
        enabled: true
    kubernetes:
      ingressEndpoint:
        useDefaultPublishedService: true
    
    acme:
      enabled: true
      logging: true
      staging: false # ステージングテストの時はここをtrueに
      challengeType: "tls-alpn-01"
      email: "[自分のメールアドレス]"
      persistence:
        enabled: true

いろいろ考えた結果、こんな感じになりました。難しい・・・

そして、後は同じようにkubectl apply -f traefik.yamlでデプロイできます。

これでIngress Controllerの設定は完了なはず。次は実際にサービスを公開するためのingress設定を書くことになります。

ちなみに、デフォルト設定で書かれているspec.setは上手く動作しない可能性が高いようです。spec.valuesContentは正常に動きます。

この不具合に1週間ほどハマって全然起動しなかった・・・ このあたりはまだまだドキュメントが無いってのも辛いです。

HelmChart CRD doesn't support objects in spec.set · Issue #276 · rancher/k3s

Traefik自動起動の無効化

Traefikの設定を変更しましたが、実はk3s Serverは起動した瞬間にデフォルト設定を再度読み込みデプロイを行う仕様があるため、つまり再起動するとデフォルト設定に戻されてしまいます。

yamlも初期化されてしまうため、別の場所にコピーしたというわけです。

そのため、以下のコマンドで再起動時に再デプロイしない設定を追加します。

k3s server --no-deploy traefik

systemdで起動しているときは、systemdの設定ファイルに追加すれば良いです。

Ingressのサービス公開設定

前回の記事で公開したサービスをIngress経由で公開設定を行います。 ingressの設定はこんな感じです。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:
  - host: whoami.k8s.potproject.net
    http:
      paths:
      - path: /
        backend:
          serviceName: whoami-service
          servicePort: 8080

backend.serviceNameに公開したいサービス、servicePortにポートを設定します。 今回は新しくサブドメインを作り、whoami.k8s.potproject.netにしました。yamlは他と同じようにkubectl applyで反映可能です。 ここにhttpsでアクセスすれば動作するという形です。

当然ながら、ドメインを使用したい場合はDNSでのドメイン設定も必要です。使用しているDNSにて、OCIのLBのPublicIPアドレスとドメインをAレコードで登録してください。

この後は同じようにkubectl apply -f ingress.ymlすればOK。

証明書発行のログを調べる

で、これでうまくいけばいいんですが上手く動かないことも多いです。 上手く動かないのでとりあえずログを見てみます。traefikのPodのログを追えばわかります。

> kubectl get pods --namespace kube-system | grep traefik
traefik-66c9745596-29b82                  1/1     Running     0          15s

> kubectl logs traefik-66c9745596-29b82 --namespace kube-system
{"level":"error","msg":"Unable to obtain ACME certificate for domains \"whoami.k8s.potproject.net\" detected thanks to rule \"Host:whoami.k8s.potproject.net\" : unable to generate a certificate for the domains [whoami.k8s.potproject.net]: acme: Error -\u003e One or more domains had a problem:\n[whoami.k8s.potproject.net] acme: error: 400 :: urn:ietf:params:acme:error:connection :: Connection refused, url: \n","time":"2019-11-16T03:22:51Z"}

うまくドメインでの接続が出来ていないようです。 ・・・そういえば、OCI側のロードバランサー設定も必要でした。

OCI ロードバランサーの設定

前回の記事で設定したOCIのロードバランサーも変更します。

まずポートを変えたので内部のポートをTCP30000に変更し、外部ポートのエンドポイントがhttpsとなったのでhttpの80ポートから443ポートに変更します。

OCIのリスナーをTCP 443に設定。 oci1

そしてバックエンドセットのバックエンドポートをTCP 30000に設定。ヘルスチェックもTCP 30000にします。OCIにhttpsを渡すことになるため、TCPでのヘルスチェックに変更せざるを得ないようです。 oci2

今回、OCIのロードバランサーで設定できるTLS証明書を使用せず、ingressでやるというややこしいことになっているのでこんなことになっています。とはいえOCIの証明書はLet's Encryptの自動更新なんてできないので・・・

後、OCIファイアウォールの443を開けるのも忘れずに。

TLS証明書発行後の疎通確認

これで証明書が無い状態での外部との疎通ができるようになりました。ここから証明書を発行するため、treafikを再起動します。

kubenatesなので、再起動のコマンドというよりはtreafikのPodsを削除すると勝手にまた立ち上がるので、それを利用します。

> kubectl delete pods traefik-66c9745596-29b82 --namespace kube-system
pod "traefik-66c9745596-29b82" deleted

これでも駄目なら、ロードバランサーがうまく返してくれないとか、設定ミスとかそんなところかと思います。

そしてアクセス。ログを見てLet's Encrypt系のエラーが無いのを確認。いけてるはず!

出来たものがこちら。実際に外部公開しています。ものすごいアクセスでも来ない限りは大丈夫でしょう。

letl

https://whoami.k8s.potproject.net/

正常にTLS証明書を取得し、https接続が出来ています。これで完成です。完璧。

所感

自己満足のいくkubernetesクラスタ環境の構築が完了しました。

ちなみに前回からの引継ぎなのでやはりここまでも無料です。やはりコストがかからないとなると気楽に試せていいですね。https化も無料ですし、いい時代ですね。

これで大体のk8sを知ることが出来、それを踏まえて本番でk8sクラスタを運用するのは結構な茨の道とは思いました。

しかしk3sは最初からIngressやPVCが用意されていることもあり、普通に構築入門みたいな記事を見てやるより、かなりとっつきやすいと思います。前回の記事見てみればとりあえず機能とかわからなくても動かすまではみんなできるはず。1行打つだけですしね。

これからも普及してもっと本番とかに投入できればいいですね・・・。本当に・・・。