概要
GKEでhttpsを用いるために、Let's Encrypt cert-managerを試した覚え書き。
jetstack/cert-manager: Automatically provision and manage TLS certificates in Kubernetes
環境
$ kubectl version Client Version: version.Info{Major:"1", Minor:"8", GitVersion:"v1.8.6", GitCommit:"6260bb08c46c31eea6cb538b34a9ceb3e406689c", GitTreeState:"clean", BuildDate:"2017-12-21T06:34:11Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"} Server Version: version.Info{Major:"1", Minor:"9+", GitVersion:"v1.9.6-gke.1", GitCommit:"cb151369f60073317da686a6ce7de36abe2bda8d", GitTreeState:"clean", BuildDate:"2018-04-07T22:06:59Z", GoVersion:"go1.9.3b4", Compiler:"gc", Platform:"linux/amd64"} $ helm version Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"} Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
証明書の自動発行プロトコルについて
Let's Encryptの証明書発行を行うためにACMEというプロトコルがあります。
これはLet's Encryptが策定したSSL/TLS証明書発行のためのプロトコルで、IETFで標準化されています。
ietf-wg-acme/acme
ACMEを用いた証明書発行方法は何パターンか手段がありますが、cert-managerではHTTPもしくはDNSを用いる方法がサポートされています。
手順
GKE Clusterの作成
少しでも節約したいのでプリエンプティブインスタンスを使う以外は特に未設定
$ gcloud beta container clusters create "sample-cluster" \ --zone "asia-northeast1-a" \ --preemptible --num-nodes "3" \ --cluster-version "1.9.6-gke.1"\ --machine-type "n1-standard-1"
Helmのインストール
$ helm init
だけだと権限が足りないので、cluster-adminを付与
$ brew install kubernetes-helm $ kubectl create serviceaccount tiller --namespace kube-system $ kubectl create clusterrolebinding tiller --clusterrole=cluster-admin --serviceaccount=kube-system:tiller $ helm init --upgrade --service-account tiller
GCPのService Accountを作成
DNS-01 Challengeを行うためにはKubernetesからDNSを編集する必要がある。
そのためのGCPのService Accountを発行と、発行したService AccountをKubernetesへ渡す。
$ export GCLOUD_PROJECT=$(gcloud config get-value project) $ gcloud iam service-accounts create cert-manager --display-name "cert-manager" $ gcloud projects add-iam-policy-binding ${GCLOUD_PROJECT} --member serviceAccount:cert-manager@${GCLOUD_PROJECT}.iam.gserviceaccount.com --role roles/dns.admin $ gcloud iam service-accounts keys create cert-manager-key.json --iam-account cert-manager@${GCLOUD_PROJECT}.iam.gserviceaccount.com $ kubectl create secret generic clouddns-service-account --from-file=cert-manager-key.json=cert-manager-key.json -n kube-system
cert-managerのインストール
helmでサクッと
$ helm install stable/cert-manager --namespace kube-system
ClusterIssuer/Certificateの用意
Issuerには Issuer
と ClusterIssuer
の2種類あります。
Issuer
は単一namespaceを対象にしているのに対して、 ClusterIssuer
はクラスター全体を対象にしていることが違いになります。
Issuerの役割としては「証明書の認証局についての設定」です。
// 今後Let's Encrypt以外の証明書発行業者が対応し始めたらserverのURLが変わったりするイメージなのかなーと。
ClusterIssuerを作成するために clusterissuer.yaml
を以下のように記述します。
letsencrypt-staging
と letsencrypt-prod
はそれぞれ開発用と本番用にLet's Encryptが用意してくれている証明局になります。
Staging Environment - Let's Encrypt - Free SSL/TLS Certificates
apiVersion: certmanager.k8s.io/v1alpha1 kind: ClusterIssuer metadata: name: letsencrypt namespace: kube-system spec: acme: server: https://acme-v01.api.letsencrypt.org/directory email: '' #TODO: YOUR EMAIL ADDRESS privateKeySecretRef: name: letsencrypt http01: {} dns01: providers: - name: gcp-dns clouddns: serviceAccountSecretRef: name: clouddns-service-account key: cert-manager-key.json project: '' #TODO: YOUR GCP PROJECT
次にCertificateを作成します。
これは証明書自体の設定で、扱うドメインについて記述します(SANも対応)。
Certificateを作成するために certificate.yaml
を以下のように記述します。
apiVersion: certmanager.k8s.io/v1alpha1 kind: Certificate metadata: name: '' #TODO: certificate name spec: secretName: cert-manager-tls issuerRef: name: letsencrypt kind: ClusterIssuer commonName: '' #TODO: YOUR DOMAIN dnsNames: - '' #TODO: YOUR DOMAIN acme: config: - dns01: provider: gcp-dns domains: - '' #TODO: YOUR DOMAIN
最後に記述したmanifestをそれぞれ適用させます。
$ kubectl apply -f clusterissuer.yaml -n kube-system $ kubectl apply -f certificate.yaml
サンプルアプリの構築
雑にnginxを動かす例
Deployment/Service
apiVersion: apps/v1 kind: Deployment metadata: name: web labels: app: web spec: replicas: 2 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: nginx image: nginx:1.13 ports: - containerPort: 80 --- kind: Service apiVersion: v1 metadata: name: web-service spec: type: NodePort selector: app: web ports: - protocol: TCP port: 80 targetPort: 80 --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: web-ingress spec: tls: - secretName: cert-manager-tls hosts: - '' #TODO: YOUR DOMAIN backend: serviceName: web-service servicePort: 80
ドメインの設定
ingressのIPをCloud DNSへ登録。
反映にちょっと時間が必要
$ export ZONE=<YOUR CLOUD DNS ZONE> $ export DOMAIN=<YOUR DOMAIN> $ export INGRESS_IP=$(kubectl get ing -o jsonpath='{.items[].status.loadBalancer.ingress[].ip}') $ export GCLOUD_PROJECT=$(gcloud config get-value project) $ gcloud dns --project=$GCLOUD_PROJECT record-sets transaction start --zone=$ZONE $ gcloud dns --project=$GCLOUD_PROJECT record-sets transaction add $INGRESS_IP --name=${DOMAIN}. --ttl=300 --type=A --zone=$ZONE $ gcloud dns --project=$GCLOUD_PROJECT record-sets transaction execute --zone=$ZONE
おまけ:HSTS
Cloud LoadBalancerは /
のパスで200を返す必要があるので、以下のようなnginxコンフィグで対応すると良いかもです。
/etc/nginx/nginx.conf
user nginx; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$server_name $remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; # Cloud LoadBalancer用ヘルスチェック server { listen 80; location / { add_header Content-Type "text/plain"; return 200 "ok"; } } include /etc/nginx/conf.d/*.conf; }
/etc/nginx/conf.d/default.conf
server { server_name example.com; listen 80; if ($http_x_forwarded_proto != https) { return 301 https://$host$request_uri; } location / { root /usr/share/nginx/html; index index.html index.htm; } }