InfraStudyのIaC回に触発されてTerraformを触ったので構築めも
プロジェクトの作成
GCPのダッシュボードから適当にプロジェクトを作り,$PROJECT_IDにプロジェクトIDをセットしているところから始める.
デフォルトのプロジェクトを設定
$ gcloud config set project $PROJECT_ID
GKE, GCR(Google Container Registry)APIの有効化
$ gcloud services enable container.googleapis.com containerregistry.googleapis.com
terraformが利用するサービスアカウントの作成.
$ TF_ACCOUNT=terraform $ gcloud iam service-accounts create $TF_ACCOUNT --display-name="terraform-account" --project=$PROJECT_ID
作成したサービスアカウントのキーペアを作成し,秘密鍵をダウロードする.
$ KEY_SAVE_PATH=~/key.json $ gcloud iam service-accounts keys create $KEY_SAVE_PATH --iam-account $TF_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
サービスアカウントに対するロールの付与.
$ gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$TF_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com --role roles/compute.admin # GKEノードプールの操作に必要 $ gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$TF_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com --role roles/storage.admin # GCRの操作に必要 $ gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$TF_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com --role roles/container.clusterAdmin # GKEクラスタの操作に必要 $ gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$TF_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com --role roles/iam.serviceAccountUser # サービスアカウントへのアクセスに必要
terraformが利用するサービスアカウント情報をセットする.terraform applyなどでリソース操作する際は,このサービスアカウントの権限で実行されるように設定する.
$ export GOOGLE_APPLICATION_CREDENTIALS=$KEY_SAVE_PATH
GKEのセットアップ
terraformの設定を書く.今回はコンテナネイティブの負荷分散を利用するため,VPCネイティブクラスタを作成する.コンテナネイティブの負荷分散とは,IngressプロキシからCompute Engineロードバランサを介さずに直接Podへ通信を流す手法である.従来のCompute Engineロードバランサをバックエンドに使う方法と比較して1ホップ減るためレイテンシの削減などが見込まれるとして,GCPもこちらを推奨している.
main.tf
# 環境変数から読み込ませる
variable "project_id" {}
provider "google" {
project = var.project_id
region = "asia-northeast1-a"
}
resource "google_container_cluster" "primary" {
name = "sample-cluster"
location = "asia-northeast1-a"
# ノードプールとクラスタを分けて作成したいが,ノードプールのないクラスタは作成できない
# そのため小さなノードプールを作成してすぐに削除する.
remove_default_node_pool = true
initial_node_count = 1
# VPCネイティブクラスタを作成するために必要
ip_allocation_policy {
cluster_ipv4_cidr_block = ""
services_ipv4_cidr_block = ""
}
master_auth {
username = ""
password = ""
client_certificate_config {
issue_client_certificate = false
}
}
}
resource "google_container_node_pool" "primary_nodes" {
name = "sample-node-pool"
location = "asia-northeast1-a"
cluster = google_container_cluster.primary.name
node_count = 3
node_config {
machine_type = "n1-standard-1"
metadata = {
disable-legacy-endpoints = "true"
}
# devstorage.read_onlyはGCRからイメージをpullするために必要
oauth_scopes = [
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
"https://www.googleapis.com/auth/devstorage.read_only",
]
}
}
project_idは環境変数から読み込ませて実行する.TF_VAR_$variable環境変数がtfファイルの$variable変数として使われる.
$ export TF_VAR_project_id=$PROJECT_ID $ terraform apply
kubectlのコンテキストを今作成したクラスタに切りかえる.ちなみにコンテキストの管理や確認にはkubectlがお勧め
$ gcloud container clusters get-credentials --zone=asia-northeast1-a sample-cluster
ノードが作成されているか確認する.
$ kubectl get nodes NAME STATUS ROLES AGE VERSION gke-sample-cluster-sample-node-pool-83bcffdf-kb4z Ready <none> 40m v1.14.10-gke.36 gke-sample-cluster-sample-node-pool-83bcffdf-pb4j Ready <none> 40m v1.14.10-gke.36 gke-sample-cluster-sample-node-pool-83bcffdf-zl2s Ready <none> 39m v1.14.10-gke.36
アプリケーションの作成
コーディング
続いてGoでアプリケーションを書いていく.
とりあえずmoduleを作成する.
$ MODULE_NAME=gcp-example $ go mod init $MODULE_NAME
メッセージを返す単純なWebアプリを作る.
main.go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Dockerfileを作る.マルチステージビルドを利用し,2ステージ目ではscratchイメージを使ってイメージサイズを可能な限り小さくしている.この際,scratchにはユーザ作成のコマンドが無いので,ユーザとグループは1ステージ目で作る必要がある.
また,GoをビルドするときにCGO_ENABLED=0を指定しないと実行時にエラーになる.
FROM golang:1.13 as builder WORKDIR /go/src/app RUN groupadd -g 10001 myapp && useradd -u 10001 -g myapp myapp COPY go.mod ./ COPY . ./ RUN CGO_ENABLED=0 go build -o /go/bin/app FROM scratch COPY --from=builder /go/bin/app /go/bin/app COPY --from=builder /etc/group /etc/group COPY --from=builder /etc/passwd /etc/passwd USER myapp CMD [ "/go/bin/app" ]
Dockerfileをビルドする.
$ docker build -t helloapp:1.0 .
テストしてみる.
$ docker run --rm --name helloapp -d -p 8080:8080 helloapp:1.0 && curl localhost:8080 && docker stop helloapp 1f535c499c78517612afc0d2a20d3b716ca5f7ab70d7b720eb31ad6249df4687 hello helloapp
GCRにpush
まずはGCRへのリクエストを認証するようにDockerを構成する.
$ gcloud auth configure-docker
dockerイメージにレジストリ名をタグ付けする.こうすることで,docker pushした際に指定したレジストリにpushされる.
$ docker tag helloapp:1.0 gcr.io/$PROJECT_ID/helloapp:1.0
GCRにpushする.
$ docker push gcr.io/$PROJECT_ID/helloapp:1.0
確認してみる
$ gcloud container images list NAME gcr.io/<project_id>/helloapp
GKEにデプロイ
k8sマニフェストを書いていく.deployment→service→ingressの順で適応されるようにファイル名に接頭辞をつける.kubectl applyはファイル名の辞書順で行われるので,これが無いとserviceの前にingressがapplyされて依存関係上エラーになる(なった).
$ tree kube/ kube/ ├── 01-deployment.yaml ├── 02-service.yaml └── 03-ingress.yaml
01-deployment.yaml
Deploymentのマニフェスト.先ほどGCRにpushしたイメージを指定している.resource.limitsとresource.requestsは共に指定しておくのがベター.
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-dep
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: hello-app
image: gcr.io/<project_id>/helloapp:1.0
resources:
limits:
memory: "128Mi"
cpu: "200m"
requests:
memory: "128Mi"
cpu: "100m"
ports:
- containerPort: 8080
02-service.yaml
Network Endpoint Group(NEG)を利用してコンテナネイティブの負荷分散を行う.これはデフォルトの動作では無いため,IngressのバックエンドとなるServiceにはアノテーションをつける.
apiVersion: v1
kind: Service
metadata:
name: test-service
annotations:
cloud.google.com/neg: '{"ingress": true}'
spec:
type: NodePort
selector:
app: myapp
ports:
- port: 3000
protocol: TCP
targetPort: 8080
03-ingress.yaml
L7LBとなるIngressを作成する.バックエンドとして先ほど作成したServiceを指定する.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
backend:
serviceName: test-service
servicePort: 3000
デプロイする.
$ kc apply -f kube/
確認する.
$ kc get ingress NAME HOSTS ADDRESS PORTS AGE test-ingress * 34.120.111.236 80 15m $ curl 34.120.111.236 hello
🎉