EKS 上のアプリケーションを外部公開する際に使う Ingress コントローラとして ALB Load Balancer Controller や NGINX Ingress Controller が挙げられます。
主な違いはリバースプロキシとしてカスタマイズ性の高い nginx を利用するか AWS マネージドのサービスである Elastic Load Balancing を利用するかになります。
今回はこれらのコントローラの違いについて詳しく触れませんが、詳細は下記 Amazon Web Services ブログが参考になります。
- Kubernetes アプリケーションの公開 Part 2: AWS Load Balancer Controller
- Kubernetes アプリケーションの公開 Part 3: NGINX Ingress Controller
NGINX Ingress Controller を採用するとなった場合、 NLB で SSL 終端して ACM を利用するパターンと AWS 外で取得した証明書を Secret として登録して利用するパターンがあります。
ACM を利用することで、nginx を利用しつつも証明書の管理や更新を AWS に任せることができます。
今回は NGINX Ingress Controller を利用しつつ、NLB で SSL 終端するパターンを試してみました。
EKS クラスター作成
eksctl でクラスターを作成します。
eksctl create cluster --name test-cluster --region ap-northeast-1 --version 1.24 --vpc-cidr 10.0.0.0/16 --without-nodegroup --with-oidc --zones ap-northeast-1a,ap-northeast-1c
続いてノードグループも作成します。
eksctl create nodegroup --cluster test-cluster --region ap-northeast-1 --name test-cluster-ng --node-ami-family AmazonLinux2 --node-type t3.medium --nodes 2 --nodes-min 1 --nodes-max 2 --node-private-networking
証明書の作成
次に利用する証明書を ACM で発行します。
今回はホストベースルーティングを利用したかったためワイルドカード証明書を作成しました。
NGINX Ingress Controller のインストール
Installation Guide に沿って、 NGINX Ingress Controller をインストールします。
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/aws/deploy.yaml
ダウンロードしたファイルの一部を書き換えます。
ConfigMap(ingress-nginx-controller) の proxy-real-ip-cidr
を EKS クラスターがデプロイされている VPC の CIDR に変更します。
Service(ingress-nginx-controller) の service.beta.kubernetes.io/aws-load-balancer-ssl-cert
を ACM で発行した証明書の ARN に変更します。
※ ここで Service(ingress-nginx-controller) の externalTrafficPolicy
を Cluster
に設定しておかないと、 Ingress Controller が存在する Node にある Pod にしかトラフィックを流さなくなります。必ずしも変更する必要はありませんが、気になった場合は変更して下さい。
- Why is my worker node status "Unhealthy" when I use the NGINX Ingress Controller with Amazon EKS?
- Health check endpoints /healthz are constantly Unhealthy in AWS NLB target groups - 503 HTTP error
修正が終わったら、 NGINX Ingress Controller をデプロイします。
$ kubectl apply -f deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
アプリケーションのデプロイ
まず、アプリケーション用の名前空間を作成します。
$ kubectl create namespace apps
namespace/apps created
次に Service リソースを作成します。
今回は Google が公開している hello と表示するだけのコンテナイメージを利用します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: first
namespace: apps
labels:
app.kubernetes.io/name: first
spec:
selector:
matchLabels:
app.kubernetes.io/name: first
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: first
spec:
terminationGracePeriodSeconds: 0
containers:
- name: first
image: gcr.io/google-samples/hello-app:1.0
ports:
- name: app-port
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: first
namespace: apps
labels:
app.kubernetes.io/name: first
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: first
ports:
- name: svc-port
port: 3000
targetPort: app-port
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: second
namespace: apps
labels:
app.kubernetes.io/name: second
spec:
selector:
matchLabels:
app.kubernetes.io/name: second
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: second
spec:
terminationGracePeriodSeconds: 0
containers:
- name: second
image: gcr.io/google-samples/hello-app:1.0
ports:
- name: app-port
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: second
namespace: apps
labels:
app.kubernetes.io/name: second
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: second
ports:
- name: svc-port
port: 3001
targetPort: app-port
protocol: TCP
$ kubectl apply -f service.yaml
deployment.apps/first created
service/first created
deployment.apps/second created
service/second created
Ingress リソースをデプロイします。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: apps-ingress
namespace: apps
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 100m
spec:
rules:
- host: first.masutaro99.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: first
port:
number: 3000
- host: second.masutaro99.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: second
port:
number: 3001
$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/apps-ingress created
レコード作成
Route53 で設定したホスト名 (first.masutaro99.com, second.masutaro99.com) からNLB へのエイリアスレコードを作成します。
動作確認
それぞれのホストに対してリクエストを送り、それぞれ HTTPS で接続できていることを確認できました。
$ curl -v https://first.masutaro99.com
* Trying 57.180.91.200:443...
* Connected to first.masutaro99.com (57.180.91.200) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: CN=*.masutaro99.com
* start date: Oct 22 00:00:00 2023 GMT
* expire date: Nov 19 23:59:59 2024 GMT
* subjectAltName: host "first.masutaro99.com" matched cert's "*.masutaro99.com"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: first.masutaro99.com
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 22 Oct 2023 12:38:35 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 62
< Connection: keep-alive
<
Hello, world!
Version: 1.0.0
Hostname: first-7c5db465f5-jsmdb
* Connection #0 to host first.masutaro99.com left intact
$ curl -v https://second.masutaro99.com
* Trying 57.180.91.200:443...
* Connected to second.masutaro99.com (57.180.91.200) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: CN=*.masutaro99.com
* start date: Oct 22 00:00:00 2023 GMT
* expire date: Nov 19 23:59:59 2024 GMT
* subjectAltName: host "second.masutaro99.com" matched cert's "*.masutaro99.com"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: second.masutaro99.com
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 22 Oct 2023 12:39:08 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 63
< Connection: keep-alive
<
Hello, world!
Version: 1.0.0
Hostname: second-64966675f9-dtr4g
* Connection #0 to host second.masutaro99.com left intact