GKEでLoadBalancerを使うのをやめて費用を抑える

GKE でプリエンプティブルの VM を利用すると Kubernetes クラスタ自体の料金はとても安いのですが、LoadBalancer を使うと約 18 ドルがかかります。ここを抑えるためにいろいろ調べていたところ great article を偶然見つけました。とても感謝です。

この投稿のとおりにすれば 18 ドルを 8 ドル(e2-micro)に抑えることができそうです。

自分の理解ですが、この仕組みはプリエンプティブルではない VM ノードを 1 台追加して、そこで traefik を実行して、
traefik からプリエンプティブルの VM ノードにルーティングしているのだと思います。
traefik を実行するノードをプリエンプティブルではないようにしているのは意図せずダウンしては困るからでしょう。また、このノードに割り振った IP アドレスを Cloud DNS に設定することになります。

traefik はリバースプロキシです。ルーティング設定を動的に変更できたりするすごいやつです。

実行に際しての注意点ですが、現時点では f1-micro で kubernetes クラスタを構築できないので e2-micro に変えておきます。

以下、自分用のメモ

1
2
terraform init
terraform apply

途中のAggressive optimizationsの項目はスキップしました。

1
2
cd k8s/kujilabo-blog
kubectl apply -f namespace.yaml

k8s/kujilabo-blog/namespace.yaml

1
2
3
4
5
6
kind: Namespace
apiVersion: v1
metadata:
name: kujilabo-blog
labels:
name: kujilabo-blog
1
2
3
gcloud iam service-accounts list
gcloud iam service-accounts keys create keys/kubeip-key.json --iam-account kubeip-serviceaccount@cocotola.iam.gserviceaccount.com
kubectl create secret generic kubeip-key --from-file=keys/kubeip-key.json -n kube-system
1
2
3
4
cd k8s/kubeip
kubectl apply -f serviceaccount.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml

正常に kubeip が稼働すると外部 IP で定義した static-ingress の IP がノードと関連づきます。

1
2
3
4
5
cd k8s/traefik
kubectl apply -f namespace.yaml
kubectl apply -f serviceaccount.yaml
kubectl apply -f configmap.yaml
kubectl apply -f resource.yaml
1
2
gcloud iam service-accounts keys create cert-manager-key.json --iam-account cert-manager@cocotola.iam.gserviceaccount.com
kubectl create secret generic clouddns-service-account --from-file=cert-manager-key.json=cert-manager-key.json -n kujilabo-blog

該当のネームスペースに clouddns-service-account が登録されていることを確認。

1
kubectl get secrets -n kujilabo-blog
1
2
NAME                       TYPE                                  DATA   AGE
clouddns-service-account Opaque 1 3s

Cloud DNS の初期設定

Google Domains で取得しているドメインの DNS の設定を開き、Use custom name serversを選択し、GCP の NS の値を設定します。

例:

1
2
3
4
ns-cloud-c1.googledomains.com.
ns-cloud-c2.googledomains.com.
ns-cloud-c3.googledomains.com.
ns-cloud-c4.googledomains.com.

以降、DNS の設定は Cloud DNS の設定画面からおこないます。

cert-manager をインストール

1
2
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.1/cert-manager-legacy.yaml
kubectl get all -n cert-manager
1
kubectl apply -f issuer.yaml

cert-kujilabo/issuer.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: kujilabo-issuer
namespace: kujilabo-blog
spec:
acme:
email: your@mail
server: https://acme-v02.api.letsencrypt.org/directory
#server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: kujilabo-issuer
solvers:
- dns01:
cloudDNS:
project: cocotola
serviceAccountSecretRef:
name: clouddns-service-account
key: cert-manager-key.json
1
kubectl apply -f certificate.yaml

cert-kujilabo/certificate.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: kujilabo-com
namespace: kujilabo-blog
spec:
secretName: kujilabo-com-tls
issuerRef:
name: kujilabo-issuer
dnsNames:
- kujilabo.com
- www.kujilabo.com

状況を確認するコマンド

1
2
kubectl describe certificate -n kujilabo-blog
kubectl describe certificaterequest -n kujilabo-blog
1
kubectl get secrets -n kujilabo-blog
1
kujilabo-com-rq9dq         Opaque                                1      81s
1
kujilabo-com-tls           kubernetes.io/tls                     2      11s
1
2
cd k8s/traefik
kubectl apply -f route-kujilabo-blog.yaml

traefik/route-kujilabo-blog.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: kujilabo-blog
namespace: kujilabo-blog
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/`)
kind: Rule
services:
- name: kujilabo-blog
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: kujilabo-blog-tls
namespace: kujilabo-blog
spec:
entryPoints:
- websecure
routes:
- match: PathPrefix(`/`)
kind: Rule
services:
- name: kujilabo-blog
port: 80
tls:
secretName: kujilabo-com-tls

kuberenetes.yaml.tpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
apiVersion: apps/v1
kind: Deployment
metadata:
name: kujilabo-blog
namespace: kujilabo-blog
labels:
app.kubernetes.io/name: kujilabo-blog
app.kubernetes.io/part-of: kujilabo-blog
spec:
replicas: 1
selector:
matchLabels:
run: kujilabo-blog
app.kubernetes.io/name: kujilabo-blog
app.kubernetes.io/part-of: kujilabo-blog
template:
metadata:
labels:
run: kujilabo-blog
app.kubernetes.io/name: kujilabo-blog
app.kubernetes.io/part-of: kujilabo-blog
spec:
nodeSelector:
cloud.google.com/gke-nodepool: web-pool
containers:
- image: gcr.io/cocotola/kujilabo-blog-app:COMMIT_SHA
imagePullPolicy: IfNotPresent
name: kujilabo-blog
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: kujilabo-blog
namespace: kujilabo-blog
spec:
type: ClusterIP
ports:
- protocol: TCP
name: tcp-80
port: 80
targetPort: 80
- protocol: TCP
name: tcp-443
port: 443
targetPort: 443
selector:
app.kubernetes.io/name: kujilabo-blog
app.kubernetes.io/part-of: kujilabo-blog