Q. なんでそんなことするの?
A. GKE使ってたら無料分(300$)が一瞬でなくなったので、GKE使わずに同じことやったらどのくらいお値段が変わるのかと思って。
環境
GCPのプロジェクトは既にあるものとします。手元で認証は済んでいるものとします。
また、GCPリソースの管理にはterraformを使います。
$ terraform version
Terraform v1.4.5
on linux_amd64
Kubernetesリソース管理にはkubectlとhelmfileを使います。
$ kubectl version --short
Flag --short has been deprecated, and will be removed in the future. The --short output will become the default.
Client Version: v1.26.3
Kustomize Version: v4.5.7
Server Version: v1.26.3
$ helmfile version
helmfile version v0.144.0
VMを複数台立てる
GCPではCompute Engineと呼ばれるVMを利用することができる。
Kubernetesクラスタのmasterノードとworkerノードを用意する。今回は一台ずつ。
provider "google" {
project = "your_project"
region = "us-central1"
}
locals {
master_tag = "k8s-master"
worker_tag = "k8s-worker"
}
resource "google_compute_address" "master" {
name = "k8s-master-address"
}
resource "google_compute_instance" "master" {
name = "k8s-master"
machine_type = "n1-standard-2"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "ubuntu-1804-lts"
}
}
network_interface {
network = "default"
access_config {
nat_ip = google_compute_address.master.address
}
}
tags = [local.master_tag]
}
resource "google_compute_instance" "worker" {
count = 1
name = "k8s-worker-${count.index}"
machine_type = "n1-standard-2"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "ubuntu-1804-lts"
}
}
network_interface {
network = "default"
access_config {
}
}
tags = [local.worker_tag]
}
とりあえず立てるだけ。zoneとかimageとかはお好きに。
マシンタイプがデフォルトだとスペックが足らなかったのでmachine_type = "n1-standard-2"
を指定する。
Kubernetesクラスタを構築する
masterノードを作る
コンソールのCompute Engineの画面で、「接続」 > 「SSH」 > 「gloudコマンドを表示」で表示されるコマンドを打つとインスタンスにsshできる。
さっきmasterとして作ったインスタンスのシェルで作業をする。
# Kubernetesリポジトリの追加
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
# もろもろインストール
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
# コンテナランタイムとしてContainerdを使う
$ sudo apt-get install -y containerd
$ sudo mkdir -p /etc/containerd
$ containerd config default | sudo tee /etc/containerd/config.toml
$ sudo systemctl restart containerd
# kubeapiを外部に曝すための設定
$ sudo mkdir -p /etc/kubernetes/
$ echo 'apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
networking:
podSubnet: 10.244.0.0/16
apiServer:
certSANs:
- "localhost"
- "192.168.1.100"
- "10.0.0.1"
controlPlaneEndpoint: "EXTERNAL_IP:6443"' > /etc/kubernetes/kubeadm.yaml
# クラスタのセットアップ
$ sudo kubeadm init --config /etc/kubernetes/kubeadm.yaml
# セットアップが終わった後に続くメッセージに従って認証情報を設置
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Podネットワークの設定
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
この時点でmasterノードでkubectl
コマンドが使えるはず。
# 自分のマシンからkubectlコマンドを叩きたいので6443ポートを開ける
resource "google_compute_firewall" "k8s_master" {
name = "k8s-master"
network = data.google_compute_network.default.self_link
allow {
protocol = "tcp"
ports = ["6443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = [local.master_tag]
}
masterノードの~/.kube/config
をそのまま自分のマシンにコピーすれば自分のマシンからkubectlコマンドが叩けるようになる。やったぜ。
workerノードを作ってクラスタにジョインさせる
workerノードのシェルで作業をする。コマンド打ちたくなければインスタンスのセットアップスクリプトみたいなやつが設定できるはずなのでそうしてください。
$ sudo apt-get update && sudo apt-get install -y apt-transport-https curl gnupg2
$ sudo apt-get update && sudo apt-get install -y containerd
$ sudo mkdir -p /etc/containerd
$ sudo containerd config default | sudo tee /etc/containerd/config.toml
$ sudo sed -i '/^GRUB_CMDLINE_LINUX=/s/"$/ systemd.unified_cgroup_hierarchy=1"/' /etc/default/grub
$ sudo update-grub
$ sudo reboot
$ sudo apt-get update && sudo apt-get install -y apt-transport-https curl gnupg2 software-properties-common
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubeadm kubelet kubectl
# masterノードでkubeadm initを終えるとjoinするためのコマンドが表示される
$ sudo kubeadm join masterノードのIP:6443 --token トークン --discovery-token-ca-cert-hash sha256:HASH
kubectl get nodes
でmasterとworkerがそれぞれいることが確認できればOK。
クラスタにwebサーバーをデプロイする
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: nginx-configmap
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configmap
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Hello, Workd!</title>
<meta charset="UTF-8">
</head>
<body>
<h1>おはよう世界</h1>
</body>
</html>
ただHTMLファイルを吐き出すだけのnginx。
kubectl apply -f manifests.yaml
でクラスタにデプロイする。
kubectl get pods
でpodがいることを確認し、kubectl port-forward Pod名 8080:80
しながらlocalhsotにアクセスして「おはよう世界」が表示されればOK。
Ingressで外部に公開
とりあえず外から見られるようにしたい。
ingress-nginxを使う。helmfileで管理する。
repositories:
- name: ingress-nginx
url: https://kubernetes.github.io/ingress-nginx
releases:
- name: nginx-ingress
namespace: ingress-nginx
chart: ingress-nginx/ingress-nginx
version: 4.6.0
values:
- controller:
service:
type: NodePort
nodePorts:
http: 30080
30080ポートに固定する。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
$ helmfile apply
$ kubectl apply -f ingress.yaml
これでノードのIP:30080
でアクセスできるようになった。うれしい。
# この時点でアクセスできるようにするには30080ポートをどこに対しても開けておくfirewallの設定をする必要がある
# あとで変える
resource "google_compute_firewall" "allow_nodeport_traffic" {
name = "allow-nodeport-traffic"
network = "default"
allow {
protocol = "http"
ports = ["30080"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = [local.worker_tag]
}
TCPロードバランサーで外部公開
固定のIPからwebサーバーにアクセスできるようにしたい。
特定のIPの80ポートで受けたらそのままworkerノードの30080ポートにTCPで転送するようにする。
# 公開用のIPを予約
resource "google_compute_global_address" "lb" {
name = "k8s-lb-address"
}
# ポート30080で待ち構えるインスタンスグループをつくる(今回は一台しかいないが)
resource "google_compute_instance_group" "worker_group" {
name = "k8s-worker-group"
zone = "us-central1-a"
instances = [for worker in google_compute_instance.worker : worker.self_link]
named_port {
name = "http"
port = 30080
}
}
# 特定のポートで受けるバックエンドサービスを定義しておく
resource "google_compute_backend_service" "backend_service" {
name = "lb"
protocol = "TCP"
port_name = "http"
backend {
group = google_compute_instance_group.worker_group.self_link
}
health_checks = [
google_compute_health_check.health_check.self_link
]
}
# 通れば何でもいいヘルスチェック
resource "google_compute_health_check" "health_check" {
name = "k8s-healthcheck"
check_interval_sec = 5
timeout_sec = 5
healthy_threshold = 2
unhealthy_threshold= 2
tcp_health_check {
port = "30080"
proxy_header = "NONE"
}
}
# TCPプロキシの設定
resource "google_compute_target_tcp_proxy" "target_tcp_proxy" {
name = "lb-target-proxy"
backend_service = google_compute_backend_service.backend_service.self_link
}
# TCPロードバランシングの設定(80で受ける)
resource "google_compute_forwarding_rule" "forwarding_rule" {
name = "lb-forwarding-rule"
target = google_compute_target_tcp_proxy.target_tcp_proxy.self_link
port_range = "80"
ip_address = google_compute_global_address.lb.address
ip_protocol = "TCP"
}
# 書いておくと外部IPがすぐわかって便利
output "lb-ipaddress" {
value = google_compute_global_address.lb.address
}
これで外部IPからwebサーバーにHTTPでアクセスできるようになった。
# 制限を厳しくしておく
resource "google_compute_firewall" "allow_nodeport_traffic" {
name = "allow-nodeport-traffic"
network = "default"
allow {
protocol = "tcp"
ports = ["30080", "80"]
}
# ロードバランサーからアクセスできればよい
source_ranges = ["35.191.0.0/16"]
target_tags = [local.worker_tag]
}
TLS終端の実現
HTTPSでアクセスできるようにしたい。
適当なDNSサービスで、お持ちのドメインをさっき予約したIPに紐づけておく。
この記事ではmy-domain.com
とする。
Ingress Controllerの設定
HTTPSで受けるためのポートを設定しておく必要がある。今回は30443ポートでingress-controllerがリッスンするようにする。
repositories:
- name: ingress-nginx
url: https://kubernetes.github.io/ingress-nginx
releases:
- name: nginx-ingress
namespace: ingress-nginx
chart: ingress-nginx/ingress-nginx
version: 4.6.0
values:
- controller:
config:
ssl-port: "30443"
service:
type: NodePort
nodePorts:
http: 30080
https: 30443
Ingressの設定
tlsの設定をする。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: "clusterissuer"
spec:
tls:
- hosts:
- mydomain.com
secretName: tls
ingressClassName: nginx
rules:
- host: mydomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
SSL証明書はcert-managerで管理する。
cert-managerのインストール
helmfileで管理する。
repositories:
- name: jetstack
url: https://charts.jetstack.io
releases:
- name: cert-manager
namespace: cert-manager
chart: jetstack/cert-manager
version: v1.7.1
createNamespace: true
helmfileだけだとCRDsが適用されないので別途適用する。
$ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.crds.yaml
証明書のイシュアーをデプロイする。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
# Ingressのmetadata.annotations.cert-manager.io/cluster-issueで指定した名前を同じにする
name: clusterissuer
spec:
acme:
# productionのエンドポイントは証明書発行のレートリミットが厳しめなので注意
# stagingはhttps://acme-staging-v02.api.letsencrypt.org/directory
server: https://acme-v02.api.letsencrypt.org/directory
email: yourmailaddress@maildomain.com
privateKeySecretRef:
# Ingressのspec.tls.[*].secretNameで指定したものと同じにする
name: tls
solvers:
- http01:
ingress:
class: nginx
TCPロードバランサーに30443への転送設定を追加する
resource "google_compute_instance_group" "worker_group" {
===
# 追加
named_port {
name = "https"
port = 30443
}
}
resource "google_compute_firewall" "allow_nodeport_traffic" {
===
allow {
protocol = "tcp"
# 追加
ports = ["30080", "30443", "80", "443"]
}
===
}
resource "google_compute_backend_service" "backend_service_https" {
name = "lb-https"
protocol = "TCP"
port_name = "https"
backend {
group = google_compute_instance_group.worker_group.self_link
}
# まあ同じでよいだろう
health_checks = [
google_compute_health_check.health_check.self_link
]
}
resource "google_compute_target_tcp_proxy" "target_tcp_proxy_443" {
name = "lb-target-proxy-443"
backend_service = google_compute_backend_service.backend_service_https.self_link
}
resource "google_compute_forwarding_rule" "forwarding_rule_443" {
name = "lb-forwarding-rule-443"
target = google_compute_target_tcp_proxy.target_tcp_proxy_443.self_link
port_range = "443"
ip_address = google_compute_global_address.lb.address
ip_protocol = "TCP"
}
以上の設定が適用されればhttps://yourdomain.com
で「おはよう世界」が見られる状態のはず。
おわり
大変すぎるので素直にGKE使ったほうがいい。
スペシャルサンクス:ChatGPT4