GKE で Let's Encrypt (ワイルドカード編)
まえがき
cert-manager は使わないです。
理由は、kube-lego で時間を無駄にしてしまったから。
これから kubernetes がどう変わっていくかわからないのと、
シンプルでプリミティブなシステムが好きなので、公式の方法で行いました。
なので、 3ヶ月に1回手動対応
です。
certbot インストール
brew install certbot
certbot 実行
sudo certbot certonly --manual \ --preferred-challenges dns-01 \ --server https://acme-v02.api.letsencrypt.org/directory \ -m hoge@fuga.com \ -d sample.com \ -d *.sample.com
こんな感じで、メインドメインとワイルドカード付サブドメインを引数に渡し実行。
DNSにTXTレコード追加
------------------------------------------------------------------------------- Please deploy a DNS TXT record under the name _acme-challenge.sample.com with the following value: lgfkjaldfjlasdkgjjakdflasjflaslasdjf Before continuing, verify the record is deployed. -------------------------------------------------------------------------------
↑の値をTXTレコードに追加します。
DNS名:_acme-challenge.sample.com 値:lgfkjaldfjlasdkgjjakdflasjflaslasdjf
追加したら、Enter
を押す。
登録前に押すとこけます。
あと、DNSにメインドメイン以外のAレコードがあるとこけました(ちょっとハマった)
tls用の Secret 作成
↑で作成された証明書を元にSecret作成
sudo kubectl create secret tls tls-certs \ --key /etc/letsencrypt/live/sample.com/privkey.pem \ --cert /etc/letsencrypt/live/sample.com/fullchain.pem
IngressでSecretを参照
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ing-dev spec: tls: - secretName: tls-certs backend: serviceName: svc-dev servicePort: 80
↑みたいに tls-certs
を追加する。
Ingress デプロイ後、2,3分間 経過すると反映されてます。
サブドメインをDNSに登録しワイルドカードが聞いているか確認してみましょう。
まとめ
公式のやり方でサクッと対応できました。
3ヶ月に1回の更新もシェルスクリプトで対応できそうです。
cert-manager
に対応して、破壊的変更リスクとバグリスクを抱えるより、
3ヶ月に1回の更新コストをとったという感じです。
Container-Optimized OS で Redisをホストする
GCE(Container-Optimized OS)作成
インスタンスの作成と設定 | Container-Optimized OS | Google Cloud
コンソールのブートディスクからContainer-Optimized OSを選択する
後は、任意でポチポチやるだけ
Redis コンテナ起動
コンテナ名を redis-master
コンテナイメージを redis:4-alpine
ポートを 6379
永続化(AOF)フォルダを /home/redis/data
とすると、下記コマンドでRedisコンテナ起動
docker run --name redis-master -p 6379:6379 -v /home/redis/data:/data -d redis:4-alpine redis-server --appendonly yes
GCE の startup script 設定
再起動時に redis-master
コンテナを起動するようにする
インスタンス名 redis-master-dev
とすると、下記コマンドで設定完了
sh
gcloud compute instances add-metadata redis-master-dev --metadata startup-script='#! /bin/bash
docker start redis-master'
コンテナ環境前提でGCEを使う場合 Container-Optimized OS
入れとけばおkです
コンテナ最高すぎる
GKE LBで、Ingressを使ってTLS/SSL対応する
追記
kube-legoがオワコンになったのでこの記事もオワコンになりました
GitHub - jetstack/kube-lego: Automatically request certificates for Kubernetes Ingress resources from Let's Encrypt
kube-legoのセットアップ
Kubernetes Ingress リソースを使って GCP上にHTTPSロードバランサを構築する
kube-legoを使う
github.com
コンテナクラスタは作成されている前提で進める
yuki-toida.hatenablog.com
kube-lego をクローンする
git clone https://github.com/jetstack/kube-lego.git
lego 作業フォルダに移動
cd kube-lego/examples/gce/lego/
configmap.yaml
を編集して、lego.email
を適切に設定する
kube-legoのリソースを作成する
kube-lego/examples/gce/lego/
配下にあるリソースを作成する
kubectl create -f ./ namespace "kube-lego" created configmap "kube-lego" created deployment "kube-lego" created
kube-lego
namespace に作成されているか確認する
kubectl get deployment,replicaset,pod,configmap --namespace kube-lego
Ingressリソースを作成する
DeploymentやServiceは作成されているものとする
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-dev annotations: kubernetes.io/ingress.global-static-ip-name: "ingress-dev-ip" kubernetes.io/ingress.class: "gce" kubernetes.io/tls-acme: "true" spec: tls: - hosts: - sample.com - sub.sample.com secretName: ingress-dev-tls rules: - host: sample.com http: paths: - path: backend: serviceName: service-dev servicePort: 80 - host: sub.sample.com http: paths: - path: backend: serviceName: service-dev servicePort: 80
ここで注意することは、
kubernetes.io/ingress.allow-http: "false"
のアノテーションを入れないこと、HTTPで認証を行っているので失敗する
作成後、ロードバランサーの疎通と認証が通るまで15分くらい時間かかる
kube-legoのログをみながら認証が通ったか確認する
kubectl logs -f --namespace kube-lego $(kubectl get pod --namespace kube-lego -l app=kube-lego -o name)
認証が通ったらhttps://ドメイン
で疎通確認
GCEインスタンスの自動起動停止をスケジューリングする
- 適切なサービスアカウントを作成する
- ターミナルサーバ(GCE)作成
- gcloudでGCEインスタンスを起動停止するスクリプト
- cron で定時にスクリプトが実行されるようにする
- Cloud SQL の起動停止も追加する
適切なサービスアカウントを作成する
インスタンスのサービス アカウントの作成と有効化 | Compute Engine ドキュメント | Google Cloud
GCEインスタンスをマネジメントするだけならCompute Admin
のみでおk
今回はCloudSQLも管理したいので、Cloud SQL 管理者
も追加する
Terminal Client
サービスアカウントを作成する
ターミナルサーバ(GCE)作成
cronを動かすだけなので、f1-micro(vCPU x 1、メモリ 0.6 GB)でおk
サービスアカウントに先程作ったTerminal Client
を設定する
作ったインスタンスにログインしZONEを設定する
gcloud compute ssh terminal-dev
sudo gcloud config set compute/zone asia-northeast1-a
最後に gcloud の config を確認する
gcloud config list
gcloudでGCEインスタンスを起動停止するスクリプト
redis-dev インスタンスを起動するスクリプト
terminal-cron-start.sh
として保存
gcloud compute instances start redis-dev
redis-dev インスタンスを停止するスクリプト
terminal-cron-stop.sh
として保存
gcloud compute instances stop redis-dev
cron で定時にスクリプトが実行されるようにする
crontab を設定する
sudo crontab -e
editorに何を使うか選ぶ
3の vim を選んだ(nano使えないので)
下記2行を追加、UTCタイムであることに注意
10:00(JST)に起動し、19:00(JST)に停止する
0 1 * * 1-5 sh /home/terminal-cron-start.sh 0 10 * * 1-5 sh /home/terminal-cron-stop.sh
Cloud SQL の起動停止も追加する
terminal-cron-start.sh
とterminal-cron-stop.sh
にそれぞれ以下を追加する
Cloud SQL起動スクリプト
gcloud sql instances patch db-$ENV --activation-policy ALWAYS
gcloud sql instances patch db-$ENV --activation-policy NEVER
GCEのDocker-Redisサーバに起動時実行スクリプトを設定する
yuki-toida.hatenablog.com
前回の記事でredisコンテナは作成されている前提ですすめる
今回はインスタンス起動時に停止しているredisコンテナを起動する
GCEにstartup-scriptを設定する
新しいインスタンスに設定する場合はcreate
gcloud compute instances create redis-dev --metadata startup-script='#! /bin/bash docker start redis'
既存のインスタンスに設定する場合はadd-metadata
gcloud compute instances add-metadata redis-dev --metadata startup-script='#! /bin/bash docker start redis'
これだけでインスタンス起動時にredisコンテナが起動する
インスタンスにSSH接続し、redisにアクセスできるか確認する
gcloud compute ssh redis-dev redis-cli ping pong
GKEにデプロイしHTTPロードバランシングする
- Container RegistryにアプリケーションDockerイメージをプッシュする
- deployment.yamlを記載する
- デプロイする
- service.yamlを記載する
- ingress.yamlを記載する
- 外部IPにアクセスする
Container RegistryにアプリケーションDockerイメージをプッシュする
https://cloud.google.com/container-registry/docs/pushing-and-pulling?hl=jacloud.google.com
自前でアプリケーションをビルドしたDockerfileを作成する
ビルドしContainer Registryにプッシュする
GCP_PROJECT_ID="sample-dev" GCP_IMAGE=asia.gcr.io/$GCP_PROJECT_ID/sample:0.0.1 # build docker build -f dockerfiles/Dockerfile -t $GCP_IMAGE . # push gcloud docker -- push $GCP_IMAGE
これで準備完了
kubectlを使いGKEにデプロイする
deployment.yamlを記載する
以下、レプリカ数2で作ったdeployment.yaml
詳細は省くがポイントはDBの環境変数に登録したcredentialsを使用していること
apiVersion: apps/v1beta1 kind: Deployment metadata: name: deployment-dev spec: replicas: 2 template: metadata: labels: app: sample spec: containers: - name: sample image: asia.gcr.io/sample-dev/sample:0.0.1 ports: - containerPort: 4000 args: ["foreground"] env: - name: DB_HOST value: "127.0.0.1" - name: DB_PORT value: "3306" - name: DB_USER valueFrom: secretKeyRef: name: cloudsql-db-credentials key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: cloudsql-db-credentials key: password - name: cloudsql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.09 command: ["/cloud_sql_proxy", "--dir=/cloudsql", "-instances=sample-dev:asia-northeast1:db-dev=tcp:3306", "-credential_file=/secrets/cloudsql/credentials.json"] volumeMounts: - name: cloudsql-instance-credentials mountPath: /secrets/cloudsql readOnly: true - name: ssl-certs mountPath: /etc/ssl/certs - name: cloudsql mountPath: /cloudsql volumes: - name: cloudsql-instance-credentials secret: secretName: cloudsql-instance-credentials - name: ssl-certs hostPath: path: /etc/ssl/certs - name: cloudsql emptyDir:
デプロイする
kubectl create -f deployment.yaml
実際にrsとpodが作成されているか確かめる
kubectl get deployment,rs,pod
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deploy/deployment-dev 2 2 2 2 20m NAME DESIRED CURRENT READY AGE rs/deployment-dev-763713461 2 2 2 20m NAME READY STATUS RESTARTS AGE po/deployment-dev-763713461-69r7v 2/2 Running 0 20m po/deployment-dev-763713461-7jrmx 2/2 Running 0 20m
こんな感じでPodがRunningで出力されてればおk
service.yamlを記載する
ポイントはセレクタにdeployment.yamlで記載したappラベルを記載すること
ポート4000を80にポートフォワードする
apiVersion: v1 kind: Service metadata: name: service-dev spec: type: NodePort selector: app: sample ports: - protocol: TCP port: 80 targetPort: 4000
Service作成
kubectl create -f service.yaml
ingress.yamlを記載する
Ingress での HTTP 負荷分散の設定 | Kubernetes Engine のチュートリアル | Google Cloud
作成したservice-devに対してIngressを作成する
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-dev
spec:
backend:
serviceName: service-dev
servicePort: 80
Ingress作成
kubectl create -f ingress.yaml
しばらくするとヘルスチェックが完了し動作する
実際に確認する
kubectl describe ing
Name: ingress-dev Namespace: default Address: ************ Default backend: service-dev:80 (************,************) Rules: Host Path Backends ---- ---- -------- * * service-dev:80 (************,************) Annotations: backends: {"k8s-be-30189--16e23e70a11044da":"HEALTHY"} forwarding-rule: k8s-fw-default-ingress-dev--16e23e70a11044da target-proxy: k8s-tp-default-ingress-dev--16e23e70a11044da url-map: k8s-um-default-ingress-dev--16e23e70a11044da Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 8m 8m 1 loadbalancer-controller Normal ADD default/ingress-dev 7m 7m 1 loadbalancer-controller Normal CREATE ip: ************ 7m 2m 5 loadbalancer-controller Normal Service default backend set to service-dev:30189
backendsがHEALTHYになっていたらおk
これは / に GETリクエストを投げて200を確認している
外部IPにアクセスする
Address: ************
ここにアクセスしてアプリケーションの起動と名前解決を確認する
GKEコンテナからCloudSQLにプロキシ接続する
シークレットを作成する
Google Kubernetes Engineから接続する | Cloud SQL for MySQL | Google Cloud
シークレットを作成
kubectl create secret generic cloudsql-instance-credentials \ --from-file=credentials.json=[PROXY_KEY_FILE_PATH]
ユーザーとパスワードを登録
kubectl create secret generic cloudsql-db-credentials --from-literal=username=[USER] --from-literal=password=[PASSWORD]
deployment.yamlを作成しデプロイする
deployment.yamlファイルを記述する
DB_USER, DB_PASSWORD 環境変数を設定
- name: DB_USER valueFrom: secretKeyRef: name: cloudsql-db-credentials key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: cloudsql-db-credentials key: password
CloudSQLインスタンス接続コマンド記載
- image: gcr.io/cloudsql-docker/gce-proxy:1.09 name: cloudsql-proxy command: ["/cloud_sql_proxy", "--dir=/cloudsql", "-instances=[INSTANCE_CONNECTION_NAME]=tcp:3306", "-credential_file=/secrets/cloudsql/credentials.json"] volumeMounts: - name: cloudsql-instance-credentials mountPath: /secrets/cloudsql readOnly: true - name: ssl-certs mountPath: /etc/ssl/certs - name: cloudsql mountPath: /cloudsql volumes: - name: cloudsql-instance-credentials secret: secretName: cloudsql-instance-credentials - name: ssl-certs hostPath: path: /etc/ssl/certs - name: cloudsql emptyDir:
アプリケーション側でDB_USER, DB_PASSWORDを読み込む用実装する
次回Elixirアプリケーションも記載したdeployment.yamlを作成する