はじめに
前回まではAzureを利用してKubernetesやLogicアプリなどを試してきた。今度はGoogle Cloud Platformの無料トライアルにて同じようにKubernetesを使ったりpub/subメッセージしたり、RestやEE8サービスを色々と試していこうと思う。まずは、以下のようにインターネット上からhttps://~でブラウザでアクセスできるところまでを作ろうと思う。

無料トライアルの開始
GCPの無料トライアルを利用するには、まずはgoogleアカウントが必要になります。アカウントを作成したらサインインをした上で無料トライアルの開始案内に従い2ステップで完了します。完了するには、クレジットカードの登録が必要になりますのでカードの準備が必要です。

利用規約と最新情報の通知にチェックをつけて「続行」をクリックします。

お客様様情報を入力したあとはクレジットカードを登録する必要があります。登録したら「住所は同じ」にチェックをつけて「無料トライアルを開始」をクリックします。
クレジットカードを登録したので、「無料枠を超えて勝手に引き落とされたら困るなぁ…」と思ったら、、、

こんなメッセージが出てきました。勝手に課金されることはなさそうです、安心して検証出来そうです。
GCRにイメージを保存する
まずはWebサービスのイメージをレジストリに登録します。AzureにはAzure Container Registoryというレジストリがありましたがgoogleにも同じようなネーミングのGCR(Google Container Registory)があります。ここにイメージを保存でき必要に応じてpullしたり出来ます。

Dockerfileとソースの作成
せっかくなのでHello Worldを表示するだけでなく、ホスト名も表示するアプリにします。ブラウザでアクセスした時に今どのnodeで起動しているpodにアクセスしたのかを判別できるので。これをNode.jsで作ったサンプルアプリを以下に示します。
// モジュール
const express = require('express');
// Expressアプリケーション
const app = express();
// Hello World
const HOSTNAME = process.env.HOSTNAME;
app.get('/', (req, res) => res.send('Hello World! server=' + HOSTNAME));
// Listen
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});
イメージを作成するにあたって特に変わったことをやっていませんが、簡単なコメントとともにDockerfileとpackage.jsonを載せておきます。
# 公式からimage取得する
FROM node:12.10.0-alpine
# アプリに渡したい環境変数
ENV PORT 3000
ENV NODE_ENV production
# アプリケーションディレクトリを作成する
WORKDIR /usr/local/app
# app.js と package.jsonのコピー
COPY . .
# Nodeモジュールの追加
RUN npm install
# コンテナ開放ポート
EXPOSE 3000
CMD ["npm", "start"]
{
"name": "hello",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "~4.16.1"
}
}
イメージの作成と保存
次はイメージを作成します。先ほど作成したソースファイルとDokerfileのあるディレクトリで次のようにdockerコマンドを入力してイメージを作成します。イメージの保存先の形式はHOSTNAME/PROJECT_ID/IMAGE:TAGです。詳しくはマニュアル参照。
- HOSTNAME:gcr.io、us.gcr.io、eu.gcr.io、asia.gcr.ioと4つのロケーションを指定できますがここではアジアを指定しています。
- PROJECT_ID:GCPのプロジェクトIDです
- IMAGE:保存するコンテナのイメージ名です
- TAG:イメージを識別するために付与するタグ名です
# イメージの作成
$ docker build -f Dockerfile --tag=asia.gcr.io/liquid-almanac-279508/sample:v1.1 .
Sending build context to Docker daemon 4.608kB
Step 1/8 : FROM node:12.10.0-alpine
12.10.0-alpine: Pulling from library/node
e7c96db7181b: Pull complete
95b3c812425e: Pull complete
778b81d0468f: Pull complete
28549a15ba3e: Pull complete
Digest: sha256:744b156ec2dca0ad8291f80f9093273d45eb85378b6290b2fbbada861cc3ed01
(略)
Successfully tagged asia.gcr.io/liquid-almanac-279508/sample:v1.1
# イメージの保存
$ docker push asia.gcr.io/liquid-almanac-279508/sample:v1.1
The push refers to repository [asia.gcr.io/liquid-almanac-279508/sample]
(略)
WebコンソールからContainer Registoryを確認すると、指定したパスにイメージが作成されていることが確認できます。

GKEを作成する
次にkubernetes環境を作成します。図の赤線の部分です。ホスト名を表示するサンプルアプリにしたのでノードも3つ作ってアクセスが分散されている様子を確認するようにします。

まずCloud Shellを起動します。最初にアクセスした時は初期化が必要になるので次のようにコマンドを入力します。東京の場合のリージョンはasia-northeast1になります。リージョンとゾーンの詳細はこちら。
$ gcloud init
Welcome! This command will take you through the configuration of gcloud.
(略)
[32] asia-northeast1-b
[33] asia-northeast1-c
[34] asia-northeast1-a
(略)
Did not print [24] options.
Too many options [74]. Enter "list" at prompt to print choices fully.
Please enter numeric choice or text value (must exactly match list
item): 34
以下のコマンドでクラスタを作成します。オプションでゾーンと1ゾーンあたりのノード数を指定します。
$ gcloud container clusters create gke --zone=asia-northeast1-a --num-nodes=3
しばらくすると、クラスタが出来上がります。Webコンソール上で入力しても作成出来ます。出来上がると以下のように緑のチェックと共にサイズやロケーションなどのクラスタ情報が表示されます。

Cloud shellからkubectlコマンドを入力すればクラスタのノード情報を表示でき3つのノードが起動していることが確認出来ます。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-gke-default-pool-9bd3c2e2-9inn Ready <none> 17m v1.15.11-gke.15
gke-gke-default-pool-9bd3c2e2-mtlr Ready <none> 14m v1.15.11-gke.15
gke-gke-default-pool-9bd3c2e2-umkn Ready <none> 12m v1.15.11-gke.15
Webサービスを起動する
次にWebサービスを立ち上げます。図の赤線の部分です。先程コンテナレジストリに作成したイメージをもとに3つのpodを起動し、それを内部ロードバランサーで負荷分散させます。負荷分散は次で作成するIngressからのトラフィックを受信する想定です。

Node.jsを起動
それではコンテナレジストリからイメージをpullしてレプリカを3つ作るマニフェストを作成します。ポイントとしてはimageにて先程作成したイメージを指定しているところです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec: # ======================== Deploymentのスペック =========================
replicas: 3 # レプリカ数
selector:
matchLabels: # 検索条件
app: web-nodejs
template: # ================== テンプレート =================================
metadata:
labels:
app: web-nodejs
env: production
spec:
containers:
- image: asia.gcr.io/liquid-almanac-279508/sample:v1.1 # コンテナイメージの場所
name: nodejs-container # コンテナ名
ports:
- containerPort: 3000 # ポート番号
作成したマニフェストを適用してWebサービスを起動します。
# マニフェストを適用します
$ kubectl apply -f web-deployment.yaml
deployment.apps/web-deployment configured
# ポッドが3つ起動していることを確認
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
web-deployment-579897794b-bt55m 1/1 Running 0 40m
web-deployment-579897794b-d9dkr 1/1 Running 0 19s
web-deployment-579897794b-jpp6f 1/1 Running 0 19s
# 各ノードにpodが起動していることを確認
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-deployment-579897794b-bt55m 1/1 Running 0 41m 10.28.4.4 gke-gke-default-pool-9bd3c2e2-mtlr <none> <none>
web-deployment-579897794b-d9dkr 1/1 Running 0 66s 10.28.5.7 gke-gke-default-pool-9bd3c2e2-umkn <none> <none>
web-deployment-579897794b-jpp6f 1/1 Running 0 66s 10.28.3.7 gke-gke-default-pool-9bd3c2e2-9inn <none> <none>
内部ロードバランサーを起動
Webサービスの上位におく負荷分散のマニフェストを作成します。GKEで内部でバランスさせる場合はアノテーションでcloud.google.com/load-balancer-typeにInternalを指定します。詳しくはマニュアルを参照。
apiVersion: v1
kind: Service
metadata:
name: internal-lb
annotations:
cloud.google.com/load-balancer-type: "Internal"
spec: # ======================== スペック =================================
type: LoadBalancer
ports:
- port: 80 # 自身のポート番号
targetPort: 3000 # 振り先のポート番号
protocol: TCP
selector: # =========== 接続先(POD)の条件 =============================
app: web-nodejs
マニフェストを適用して負荷分散サービスを起動します。外側のIPアドレスが払い出されるまで数十秒待ちます。
# マニフェストを適用します
$ kubectl apply -f internal-lb-service.yaml
service/internal-lb created
# サービスの起動を確認(IP払出し中)
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
internal-lb LoadBalancer 10.31.246.69 <pending> 80:32562/TCP 8s
# サービスの起動を確認(IP付与済み)
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
internal-lb LoadBalancer 10.31.246.69 10.146.0.6 80:32562/TCP 100s
負荷分散の外側のIPアドレスをcurlで叩いて、トラフィックがバランスされていることを確認します。
# curlが使えるpodを一時的に立ち上げます
$ kubectl run -it curl --image=alpine --restart=Never --rm ash
If you don't see a command prompt, try pressing enter.
/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
v3.12.0-45-g0e4d4e3558 [http://dl-cdn.alpinelinux.org/alpine/v3.12/main]
v3.12.0-52-ge766baa626 [http://dl-cdn.alpinelinux.org/alpine/v3.12/community]
OK: 12732 distinct packages available
/ # apk add curl
(1/4) Installing ca-certificates (20191127-r3)
(2/4) Installing nghttp2-libs (1.41.0-r0)
(3/4) Installing libcurl (7.69.1-r0)
(4/4) Installing curl (7.69.1-r0)
Executing busybox-1.31.1-r16.trigger
Executing ca-certificates-20191127-r3.trigger
OK: 7 MiB in 18 packages
# 何度が負荷分散の外側のIPアドレス10.146.0.6にアクセスしてみると、、、
/ # curl http://10.146.0.6
Hello World! server=web-deployment-579897794b-bt55m/ #
/ # curl http://10.146.0.6
Hello World! server=web-deployment-579897794b-d9dkr/ #
/ # curl http://10.146.0.6
Hello World! server=web-deployment-579897794b-jpp6f/ #
/ # curl http://10.146.0.6
Hello World! server=web-deployment-579897794b-jpp6f/ #
# と、3つのpodにアクセスしていることが確認できる
何度か叩くと、異なるpodにアクセスしていることが確認できるはずだ。次はIngressだ。
Ingressのインストール
前回Azureの時と同様に、Helmと呼ばれるkubernetesのパッケージマネージャを利用してIngressをインストールする。

Azureの時はhelmのバージョンが3であったがGCPはバージョン2のようである。
$ helm version
Client: &version.Version{SemVer:"v2.14.1", GitCommit:"5270352a09c7e8b6e8c9593002a73535276507c0", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.14.1", GitCommit:"5270352a09c7e8b6e8c9593002a73535276507c0", GitTreeState:"clean"}
バージョン3の場合はtillerサービスアカウントの追加などの作業が不要であったが、バージョン2では以下のように必要となる。
# tillerサービスアカウントの作成
$ kubectl create serviceaccount tiller --namespace kube-system
# アクセス権を付与
$ kubectl create clusterrolebinding tiller-binding --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
# Tillerをインストール
$ helm init --upgrade --service-account tiller
次にhelmを使ってingressをインストールするのだが、その前にIngressに付与されるIPを予約する。(この後、https化する際にDNSのAレコードを固定化したいので)

静的予約したIPアドレスを使ってIngressをインストールします。
$ helm install stable/nginx-ingress --name nginx-ingress --namespace kube-system --set rbac.create=true --set controller.publishService.enabled=true --set controller.service.loadBalancerIP="<予約したアドレス(34.84.203.181)>"
Ingressのインストールを確認します。
# ingressがデプロイされていることを確認
$ helm list
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
nginx-ingress 1 Mon Jun 8 19:01:17 2020 DEPLOYED nginx-ingress-1.39.1 0.32.0 kube-system
# 外部アドレスが予約したIPアドレスになっていることを確認
$ kubectl get svc --namespace kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-http-backend NodePort 10.31.254.4 <none> 80:31875/TCP 2d5h
nginx-ingress-controller LoadBalancer 10.31.242.183 34.84.203.181 80:31670/TCP,443:31442/TCP 27h
nginx-ingress-default-backend ClusterIP 10.31.242.121 <none> 80/TCP 27h
tiller-deploy ClusterIP 10.31.242.87 <none> 44134/TCP 47h
Ingressのマニフェスト(https化は今はまだしない)を作成します。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: nginx
spec: # ======================== スペック =================================
rules:
- host: gcp.kimkaz.info
http:
paths:
- backend: # パス指定なし(すべて)
serviceName: internal-lb # 振り先
servicePort: 80
マニフェストを適用して、インターネットからhttp://gcp.kimkaz.infoでアクセスできることを確認する。
HTTPS化(SSL証明書の自動更新)
最後にhttpsでアクセスできるようにする。Kubernetesにはcert-managerと呼ばれるSSL証明書の管理パッケージがあるのでこれを利用する。

Cert-Managerのインストール
kubernetesのマニュアルのとおりマニフェストを適用していくのだが、kubernetesのバージョンが1.15より低かったので先にあげておく。(最初にクラスタいれたときに最新化していなかった)
GCPのコンソールから「Kubernetes Engine」「クラスタ」を選択して「アップグレード」をクリックする。すると、数分でバージョンが上がる。

注意書きにあるように、GKEの場合はマニフェスト適用に先立って以下を実行しておく。これを実行していないとアクセス権の関連で失敗することがあるらしい。
$ kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole=cluster-admin \
--user=$(gcloud config get-value core/account)
cert-managerのマニフェストを適用する。
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.15.1/cert-manager.yaml
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
(略)
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
cert-manager関連のpodが3つ上がっていることを確認する。
$ kubectl get pod --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-57f89dbdf6-gs88q 1/1 Running 0 2m15s
cert-manager-cainjector-6c78fb8b77-hd5hx 1/1 Running 0 2m15s
cert-manager-webhook-5567d8d596-ttrlz 1/1 Running 0 2m15s
ClusterIssuer、Certificateの適用
証明書の発行にあたり身元を示す発行者、ClusterIssuerリソースを作成します。メールアドレスを自身のアドレスに変えてマニフェストを適用します。
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <メールアドレス>
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: nginx
$ kubectl apply -f cluster-issuer.yaml
続いて、証明書リソースを作成して適用します。
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: gcp.kimkaz.info-tls
namespace: default
spec:
secretName: gcp.kimkaz.info-tls # 証明書のSecret名
commonName: gcp.kimkaz.info # TLS化対象のドメイン
dnsNames:
- gcp.kimkaz.info
issuerRef:
name: letsencrypt
kind: ClusterIssuer # defaultはIssuer
$ kubectl apply -f certificate.yaml
IngressでTLS終端
ドメインgcp.kimkaz.infoに先ほど指定したSSL証明書を適用するようIngressリソースを修正して適用します。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt
spec: # ======================== スペック =================================
tls:
- hosts:
- gcp.kimkaz.info
secretName: gcp.kimkaz.info-tls
rules:
- host: gcp.kimkaz.info
http:
paths:
- backend: # パス指定なし(すべて)
serviceName: internal-lb # 振り先
servicePort: 80
$ kubectl apply -f ingress.yaml
しばらくすると証明書が発行されSSLで通信ができるようになります。
# READYステイタスがtrueになっていることを確認
$ kubectl get certificate
NAME READY SECRET AGE
gcp.kimkaz.info-tls True gcp.kimkaz.info-tls 54m
$ kubectl describe certificate
Name: gcp.kimkaz.info-tls
Namespace: default
Labels: <none>
Annotations: API Version: cert-manager.io/v1alpha3
Kind: Certificate
(略)
Status:
Conditions:
Last Transition Time: 2020-06-08T13:00:03Z
Message: Certificate is up to date and has not expired
Reason: Ready
Status: True
Type: Ready
Not After: 2020-09-06T12:00:01Z
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal GeneratedKey 53m cert-manager Generated a new private key
Normal Requested 53m cert-manager Created new CertificateRequest resource "gcp.kimkaz.info-4292219798"
Normal Issued 3m55s cert-manager Certificate issued successfully
ブラウザからもhttpsでサンプル画面が表示されることを確認。

一度Azureのkubernetes環境を構築しているので、GCPでも同様の要領で作成することができた。あえて違い、手こずった箇所をあげるとすると、Ingressのインストールのところ(tillerサービスアカウントを追加)とCert-Manager(helmでなくマニフェストを適用)のところ。
今後はビルドの自動化、CI/CDサービスあたりを調べて試していきたい。