- GKE でクラスタを作成
- nginx-ingress コントローラを使用して ingress を構成
- cert-manager で Let's Encrypt
- CloudSQL にデータベースを用意
- 複数ドメイン、複数サービスで構築
CONTENTS
- 全体の構成
- クラスタの作成
- Helm セットアップ
- CloudSQL を使用するための準備
- nginx-ingress を使用してデプロイ
- Let's Encrypt で TLS 証明書を取得
- CORS 設定、DB アクセスの確認、その他
- まとめ
- 参考資料
APPENDIX
ENVIRONMENTS
- kubernetes : 1.10.4-gke.2
全体の構成
今の理解を図にしてみた。 複数ノードのクラスタにサービスが乗っかって、ロードバランサーがリクエストを捌く、というイメージ。 よくわかっていないので間違っている点もあるだろうが、現在の理解として。
クラスタの作成
GCP のコントロールパネルでクラスタを作成した。 コマンドラインから作成する方法もあるはずだが、調べていない。
クラスタのバージョンは作業時の最新だった 1.10.4-gke.2 を選択した。 この記事を書いている最中にバージョンアップが来ていたので、今後自動アップグレードの確認をしたい。
最小マシンタイプは small だろう。 micro にしたら、おそらくメモリ不足のために新しいバージョンの pod の起動がうまくいかなくなった。
HTTP 負荷分散は OFF にしておく。 これは GKE が用意した ingress controller を使用するかどうかの設定。 GKE のコントローラでは、CORS 設定がうまくいかなかった。 このため、nginx-ingress を使用する。
色々と試行錯誤したので、うまくいかないのは勘違いの可能性もある。 ただ、ドキュメントを見ると nginx-ingress の方が多機能なように見えるので、こちらを採用した。
作成を選択すると、クラスタの作成が開始。 しばらくすると GCE のインスタンスが、指定したノード数だけ立ち上がって準備完了。
Helm セットアップ
ここからは Google Cloud Shell を使用してセットアップを行なった。 gcloud と kubectl が使用できればいいので、環境があればそれを使用すれば良い。
以下のコマンドで kubectl が使用可能になる。
gcloud container clusters get-credentials \ [CLUSTER-NAME] --zone [ZONE] --project [PROJECT-NAME]
ちなみに、ワークロードの画面でシステムワークロードを選択して、メニューから KUBECTL の EDIT を選択すると shell が立ち上がってこのコマンドが表示される。
Helm Documentation を参考に Helm をインストールする。 今回は Google Cloud Shell を使用しているので、単に tar で落とした。
コマンドが使用可能になったら、サービスアカウントの設定を行う。 今回は cluster-admin を指定しているが、本来は細かく設定するべきだろう。 詳しく調べられていない。
kubectl create serviceaccount -n kube-system tiller kubectl create clusterrolebinding tiller-binding \ --clusterrole=cluster-admin \ --serviceaccount kube-system:tiller helm init --service-account tiller helm repo update
これで Helm を使用する準備が整った。
CloudSQL を使用するための準備
アプリケーションが使用するデータベースは、CloudSQL を選択した。
理由は、GKE でアプリケーションを配備するため、Google のサービスならセットアップが簡単だろうというだけ。 ベンダーをまたがってクラスタを構成するのは今後の課題。
CloudSQL に接続するための準備をしていく。
Google Kubernetes Engineから接続する | Cloud SQL for MySQL | Google Cloud
基本この通りに進めていく。
- Cloud SQL Administration API を有効化
- CloudSQL インスタンスのあるプロジェクトにサービスアカウントを作成
- Cloud SQL クライアントのロールを追加
- 「新しい秘密鍵の提供」で JSON ファイルをダウンロード
JSON ファイルは shell でアクセスできる場所にコピーしておく。 機密情報なので扱いは慎重に。
次は、CloudSQL のインスタンスに [USER]@cloudsqlproxy~%
ユーザーを追加する。
cloudsqlproxy~%
からのアクセスが出来るように。
接続にはパスワードが必須に設定してあるので、パスワードも設定した。
コンテナからこれらの機密情報にアクセスできるように、secret を作成する。
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]
PROXY_KEY_FILE_PATH
は、先にダウンロードしておいた JSON ファイルのパスを指定する。
コンテナから機密情報にアクセスするため secret を使用する、というのは理解できた。けど、secret の生成方法はこんなのでいいのか、という点については今後の課題。
これでデータベースアクセスの準備が整った。
nginx-ingress を使用してデプロイ
まず、サンプルアプリケーションを配備する。
kubectl apply -f deployment.yaml kubectl apply -f service.yaml
今回使用した deployment.yaml と service.yaml は、記事の最後に例を記載した。
ワークロードとサービスの画面で、ちやんと立ち上がっていることを確認する。 これがうまく動いていなければどうにもならない。
アプリケーションは、以下の構成にしておく。
/
: アクセス確認のために適当なレスポンスで応答/healthz
: ステータス 200 で応答- WEB アプリケーションとやりとりする部分
今回は WEB の API として使用するので、その部分は実装しておいた。
実装の詳細は公開できる準備が面倒できていないので割愛。
また、/healthz
は nginx-ingress のヘルスチェックに使用されるので、これがステータス 200 で応答しないとアプリケーションまでリクエストが来ない。
コンテナイメージが正しくできているかは、ローカルで確認しておくこと。 正しく動くかどうかをクラスタにデプロイしてから確認するようなことをすると、何が原因で失敗しているのかもう訳が分からなくなる。
アプリケーションが配備できたら、nginx-ingress をインストールする。
helm install stable/nginx-ingress \ --name [NAME] --set rbac.create=true
しばらくするとロードバランサー付きで nginx-ingress-controller が立ち上がる。
この IP アドレスを DNS に登録する。
この記事では、app1.example.com
で登録したとして進める。
nginx-ingress-controller が立ち上がったら、 ingress を登録。
kubectl apply -f ingress.yaml
今回使用した ingress.yaml は、記事の最後に例を記載した。
app1.example.com
にアクセスして正しくレスポンスが返ってくることを確認。
curl http://app1.example.com
アクセスできない場合は service.yaml の サービス名、ポートが ingress.yaml と合っているか確認。
ロードバランサーに設定が伝わるまでに、5〜10分かかることがある。 このため、うまくいってるのか、間違っているのかの判断がつらかった。
レスポンスが返ってきたら ingress の設定は完了。
デフォルトでは nginx-ingress-controller は 1つしか立ち上がらないので、これを 2 にスケールしておいた。 必要かどうかはわからない。
Let's Encrypt で TLS 証明書を取得
アプリケーションに TLS アクセスするため、ingress に TLS の設定を行う必要がある。
今回は Let's Encrypt を使用して証明書を取得する。
ここの手順どおりに作業を進めていく。
Helm のセットアップは済んでいるので、cert-manager のインストールを行う。
helm install --name cert-manager \ --namespace kube-system stable/cert-manager
cert-manager が使用する issuer を作成する。 テンプレートが用意されているので、それを使用する。
curl -sSL https://rawgit.com/ahmetb/gke-letsencrypt/master/yaml/letsencrypt-issuer.yaml | \ sed -e "s/email: ''/email: $EMAIL/g" > letsencrypt-issuer.yaml kubectl apply -f letsencrypt-issuer.yaml
issuer を作成したら、certificate を作成する。
kubectl apply -f certificate.yaml
今回使用した certificate.yaml は、記事の最後に例を記載した。
certificate を作成すると、証明書のリクエストが始まる。 取得状況は describe で確認できる。
kubectl describe -f certificate.yaml ... Type Reason Message ---- ------ ------- Warning ErrorCheckCertificate Error checking existing TLS certificate: secret "www-dogs-com-tls" not found Normal PrepareCertificate Preparing certificate with issuer Normal PresentChallenge Presenting http-01 challenge for domain foo.kubernetes.tips Normal SelfCheck Performing self-check for domain www.dogs.com Normal ObtainAuthorization Obtained authorization for domain www.dogs.com Normal IssueCertificate Issuing certificate... Normal CeritifcateIssued Certificated issued successfully Normal RenewalScheduled Certificate scheduled for renewal in 1438 hours
gke-letsencrypt/50-get-a-certificate.md at master · ahmetb/gke-letsencrypt
ここから例を引用。 実際の出力は取っておくのを忘れた。
成功すると、secret が生成されるので、ingress の設定を更新する。
kubectl apply -f ingress-tls.yaml
今回使用した ingress-tls.yaml は、記事の最後に例を記載した。
app1.example.com
にアクセスして正しくレスポンスが返ってくることを確認。
curl https://app1.example.com
デフォルトでは、tls 設定があると、https へリダイレクトするようになっている。 同じ ingress で複数ドメイン運用をするので、今後のリクエストのために http アクセスが出来るようにしておく。
annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false"
レスポンスが返ってきたら tls の設定は完了。
CORS 設定、DB アクセスの確認、その他
ここまできたら、用意しておいた WEB アプリケーションから API へアクセスできるようになっているはず。
今回はログインできることを確認できれば、CORS の設定と DB のアクセスが確認できるような構成で実装しておいた。 ここまで来るのに相当試行錯誤しているので、アクセスが成功した時はだいぶ嬉しかった。
最後に、nginx-ingress-controller に紐付いている IP アドレスを静的 IP として予約しておく。
まとめ
この構成で本番運用ができるのでは、と思っているが実際どうなるかは今後の運用次第。 今はこれで設定しました、という段階。
アプリケーションのデプロイは Container Builder を使用することで自動化できる。 これについては別な記事で。
参考資料
- Helm Documentation
- GKEからCloud SQLに接続する方法 - まーぽんって誰がつけたの?
- Google Kubernetes Engineから接続する | Cloud SQL for MySQL | Google Cloud
- ahmetb/gke-letsencrypt: Tutorial for installing cert-manager to get HTTPS certificates from Let’s Encrypt
- Ingress - Kubernetes
- Deployments - Kubernetes
- Services - Kubernetes
- Secrets - Kubernetes
- Installation Guide - NGINX Ingress Controller
- Annotations - NGINX Ingress Controller
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: app labels: app: rails spec: replicas: 2 revisionHistoryLimit: 0 selector: matchLabels: app: rails template: metadata: labels: app: rails spec: containers: - name: rails image: IMAGE ports: - containerPort: 3000 env: - name: DB_USERNAME valueFrom: secretKeyRef: name: cloudsql-db-credentials key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: cloudsql-db-credentials key: password - image: gcr.io/cloudsql-docker/gce-proxy:1.09 name: cloudsql-proxy command: ["/cloud_sql_proxy", "--dir=/cloudsql", "-instances=INSTANCE=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: volumes: - name: cloudsql-instance-credentials secret: secretName: cloudsql-instance-credentials - name: ssl-certs hostPath: path: /etc/ssl/certs - name: cloudsql emptyDir:
コンテナのイメージ IMAGE
と CloudSQL のインスタンス名 INSTANCE
を、使用しているものに置き換える必要がある。
アプリケーションからデータベースへは、ENV[DB_USERNAME]@127.0.0.1
にパスワード ENV[DB_PASSWORD]
でアクセス可能。
service.yaml
apiVersion: v1 kind: Service metadata: name: rails spec: type: NodePort selector: app: rails ports: - protocol: TCP port: 80 targetPort: 3000
ingress.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress annotations: kubernetes.io/ingress.global-static-ip-name: STATIC_IP_NAME kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/enable-cors: "false" nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD" nginx.ingress.kubernetes.io/cors-allow-origin: "https://app1.example.com" spec: rules: - host: app1.example.com http: paths: - backend: serviceName: rails servicePort: 80
静的 IP 名 STATIC_IP_NAME
とドメイン app1.example.com
を、使用しているものに置き換える必要がある。
serviceName と servicePort は service.yaml のものと合わせる。
複数ドメイン運用する場合は、cors-allow-origin
にスペース区切りで指定する。
ingress-tls.yaml
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress annotations: kubernetes.io/ingress.global-static-ip-name: STATIC_IP_NAME kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/enable-cors: "false" nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD" nginx.ingress.kubernetes.io/cors-allow-origin: "https://app1.example.com" spec: rules: - host: app1.example.com http: paths: - backend: serviceName: rails servicePort: 80 tls: - secretName: ingress-tls hosts: - app1.example.com
ingress.yaml とは、 spec.tls
の設定だけ異なる。
secretName は certificate.yaml で指定するものを使用する。
certificate.yaml
# Copyright 2018 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. apiVersion: certmanager.k8s.io/v1alpha1 kind: Certificate metadata: name: ingress-tls namespace: default spec: secretName: ingress-tls issuerRef: name: letsencrypt-prod kind: ClusterIssuer commonNames: - app1.example.com dnsNames: - app1.example.com acme: config: - http01: ingress: ingress domains: - app1.example.com
spec.issuerRef.name
には、letsencrypt-prod
と letsencrypt-staging
が使用できる。
staging の方はテスト用の証明書を取得する。 prod は取得回数に制限があるので、試行錯誤する場合は staging の方でリクエストするようにする。
spec.acme.config.http01.ingress
には、ingress.yaml で指定した ingress 名を設定する。
この設定では、 http でチェックする方式でリクエストする。 他に、dns のオプションもあるが、調べられていない。
commonNames、dnsNames、domains に、必要なドメインを複数列挙すれば、複数ドメインで証明書を取得できる。