はじめに
前回Kubernetesを利用してwebサービスを立ち上げた。その時に気になっていたSSL化とFQDNによるアクセスを実現してみようと思う。
調べたところTLS終端ができてURLパスに応じたバックエンドへのルーティングもできるIngressという機能がkubernetesに提供されているらしい。これを使おう。今回は検証用パス(/test)の一つだけしか作成しないが、パスの追加も簡単にできそうである。

環境構築
Helmを使う
IngressをインストールするにはHelmと呼ばれるkubernetesのパッケージマネージャを利用する。kubernetesにおけるHelmの位置づけは、LINUXにおけるyumやrpmと同様であり、ファイルの依存関係やアップデート、削除などが容易にできる。
Azure Cloud Shellには既にhelm clientはインストール済みで、以下のコマンドで存在とバージョンを確認ができる。バージョン2と3ではコマンド書式が違うようなので利用時には注意が必要である。さしあたって、気を付けるところはinstallコマンドのおいてバージョン2ではname指定がオプションだったのが、バージョン3では引数になって必須になっているところかと。
$ helm version
version.BuildInfo{Version:"v3.2.0", GitCommit:"e11b7ce3b12db2941e90399e874513fbd24bcb71", GitTreeState:"clean", GoVersion:"go1.13.10"}
Ingressはkubernetesの公式リポジトリ内にあるので、以下のようにしてリポジトリを追加する。
$ helm search repo stable
Error: no repositories configured
# 公式Chartリポジトリを追加
$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/
"stable" has been added to your repositories
# リポジトリにingressの追加を確認
$ helm search repo | grep nginx-ingress
stable/nginx-ingress 1.36.3 0.30.0 An nginx Ingress controller that uses ConfigMap...
Ingressのインストール
検証した後に簡単にリソースを消せるように、ここで新たな名前空間ingress-basicを作成しておきます。そこにIngressをインストールします。
# 名前空間ingress-basicを作成
$ kubectl create namespace ingress-basic
# ingressをインストール
$ helm install nginx-ingress stable/nginx-ingress \
--namespace ingress-basic \
--set controller.replicaCount=2 \
--set controller.nodeSelector."beta\.kubernetes\.io/os"=linux \
--set defaultBackend.nodeSelector."beta\.kubernetes\.io/os"=linux
Ingressコントローラとバックエンドの2つのサービスが立ち上がります。コントローラにはグローバルIPが割り当てられていることが確認できる。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 174m
nginx-ingress-controller LoadBalancer 10.0.6.130 20.44.142.64 80:32343/TCP,443:32472/TCP 11m
nginx-ingress-default-backend ClusterIP 10.0.97.127 <none> 80/TCP 11m
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-5bccf8954f-hkvhs 1/1 Running 0 12m 10.244.0.11 aks-agentpool-39326823-vmss000001 <none> <none>
nginx-ingress-controller-5bccf8954f-qsggq 1/1 Running 0 12m 10.244.1.6 aks-agentpool-39326823-vmss000000 <none> <none>
nginx-ingress-default-backend-76b8f499cb-2jpxz 1/1 Running 0 12m 10.244.1.7 aks-agentpool-39326823-vmss000000 <none> <none>
テスト用のWebサービスを作成
動作確認用にWebサービスを立ち上げておきます。

webアプリケーションをデプロイします。マニフェストは以下のとおり。コンテナイメージには、予め自身のホスト名を表示するだけのNode.jsのアプリを作成しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec: # ======================== スペック =================================
replicas: 2 # レプリカ数
selector: # ================== 検索条件 =================================
matchLabels:
app: web
template: # ================== テンプレート =============================
metadata:
labels:
app: web
env: production
spec:
containers:
- name: web-container # コンテナ名
image: registry0511.azurecr.io/web-image:v1.0 # コンテナイメージの場所
ports:
- containerPort: 3000 # ポート番号
env:
imagePullSecrets:
- name: acr-pull-key # コンテナレジストリのPULLキー
上記マニフェスト(web-deployment.yaml)を適用します。
# webサービスのデプロイ
$ kubectl apply -f web-deployment.yaml -n ingress-basic
# ポッドが2つ立ち上がっていることを確認
$ kubectl get pod -n ingress-basic
NAME READY STATUS RESTARTS AGE
web-6465b99c89-2kw9d 1/1 Running 0 9h
web-6465b99c89-7q9sn 1/1 Running 0 9h
続いて、ロードバランサ―です。ロードバランサ―はデフォルトでEXTERNAL-IPにグローバルアドレスが付与されますが、今回は内部アドレスを持たせたいので、annotationsにazure-load-balancer-internalをtrueにします。
apiVersion: v1
kind: Service
metadata:
namespace: ingress-basic
name: web-lb
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
spec: # ======================== スペック =================================
type: LoadBalancer
ports:
- port: 80 # 自身のポート番号
targetPort: 3000 # 振り先のポート番号
protocol: TCP
selector:
app: web
上記マニフェスト(lb-service.yaml)を適用します。しばらくすると、EXTERNAL-IPに内部アドレスが付与されています。
$ kubectl apply -f internal-lb-service.yaml
$ kubectl get svc -n ingress-basic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-lb LoadBalancer 10.0.241.171 10.240.0.6 80:32104/TCP 9h
FQDNの割り当て
グローバルアドレスにFQDNを割り当てるシェルを作成(createDns.sh)します。ここではホスト名をdemo-ingressとしています。シェルの詳細はAKSのマニュアルをご参照ください。
#!/bin/sh
# Public IP address of your ingress controller
IP="<グローバルアドレス>"
# Name to associate with public IP address
DNSNAME="demo-ingress"
# Get the resource-id of the public ip
PUBLICIPID=$(az network public-ip list --query "[?ipAddress!=null]|[?contains(ipAddress, '$IP')].[id]" --output tsv)
# Update public ip address with DNS name
az network public-ip update --ids $PUBLICIPID --dns-name $DNSNAME
createDns.shを実行します。Azure上のGUIからDNS名が割り当てられるが確認できます。これでインターネット上からも名前解決できるようになっているので、nslookupやdigで確認しておきます。

SSL証明書の自動更新
SSL証明書は無料のLet’s encrypを利用する。kubernetesにはcert-manaerという証明書を自動で発行管理するマネージャがあるのでそれを使う。これもAKSのマニュアルに作成用のシェルが記載されているのでそのまま使います。
#!/bin/sh
# Install the CustomResourceDefinition resources separately
kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.13/deploy/manifests/00-crds.yaml
# Label the ingress-basic namespace to disable resource validation
kubectl label namespace ingress-basic cert-manager.io/disable-validation=true
# Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
# Install the cert-manager Helm chart
helm install \
cert-manager \
--namespace ingress-basic \
--version v0.13.0 \
jetstack/cert-manager
# cert-managerがインストールされている事を確認
$ helm ls --namespace ingress-basic
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
cert-manager ingress-basic 1 2020-05-15 12:06:46.425381354 +0000 UTC deployed cert-manager-v0.13.0 v0.13.0
# cert-managerのサービスが起動されている事を確認
$ kubectl get svc --namespace ingress-basic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cert-manager ClusterIP 10.0.61.46 <none> 9402/TCP 2m16s
cert-manager-webhook ClusterIP 10.0.86.58 <none> 443/TCP 2m16s
証明書の発行にあたり身元を示す発行者、CA ClusterIssuerリソースを作成します。メールアドレスの記載を自身のアドレスに変えて適用します。
$ kubectl apply -f cluster-issuer.yaml --namespace ingress-basic
clusterissuer.cert-manager.io/letsencrypt created
$ kubectl get clusterissuer --namespace ingress-basic
NAME READY AGE
letsencrypt True 36m
$ kubectl describe clusterissuer --namespace ingress-basic
Name: letsencrypt
(略)
Status:
Acme:
Last Registered Email: <メールアドレス>
Uri: https://acme-v02.api.letsencrypt.org/acme/acct/86105715
Conditions:
Last Transition Time: 2020-05-14T05:37:58Z
Message: The ACME account was registered with the ACME server
Reason: ACMEAccountRegistered
Status: True
Type: Ready
Events: <none>
Ingressルートの作成
外部からのトラフィックを内部ロードバランサに振り分けるIngressリソースを作成します。

適用するマニフェストは以下のとおり。先ほど作成した証明書の発行者やTLSを終端するFQDN、振り分けるパス(/test)や負荷分散リソース名などを記載する。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: demo-ingress
namespace: ingress-basic
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt
spec: # ======================== スペック =================================
tls:
- hosts:
- demo-ingress.japaneast.cloudapp.azure.com
secretName: tls-secret
rules:
- host: demo-ingress.japaneast.cloudapp.azure.com
http:
paths:
- backend:
serviceName: web-lb
servicePort: 80
path: /test
$ kubectl apply -f ingress.yaml
ingress.extensions/demo-ingress created
マニフェストを適用して1分程度すると、証明書リソースが出来上がる。
$ kubectl get certificate --namespace ingress-basic
NAME READY SECRET AGE
tls-secret False tls-secret 19s
$ kubectl get certificate --namespace ingress-basic
NAME READY SECRET AGE
tls-secret True tls-secret 42s
これで、サイト名https://demo-ingres.japaneast.cloudapp.azure.comをインターネット上から閲覧できるようになっているはず。