概要
次の構成でGoアプリに対する通信ができるところまで試してみた。
なお、Qiita で既に記事を書いていそうなら載せないつもりだったけど、MetalLB v0.13で破壊的変更もあり、そのまま動かなかったので、備忘録として載せる。
環境
- Windows 10
- Ubuntu 20.04 LTS(WSL2)
- go1.19 (go version)
- Docker version 20.10.17, build 100c701
- Kind .. v0.17.0
- MetalLB .. v0.13.7
前提
- go インストール済み
- dockerインストール済み
インストール(KIND関連)
備忘録として、今回の手順を載せるが、実際やるときは一次情報源の本家のドキュメントをベースに行ってください。
-
~/go/bin にパスを通しておく
~/.bashrcへの追記例
test -d $HOME/go/bin && export PATH="$HOME/go/bin:$PATH"
-
KINDのインストール
go install sigs.k8s.io/kind@v0.17.0
インストール確認
$ $(go env GOPATH)/bin/kind version kind v0.17.0 go1.19 linux/amd64
-
クラスタの作成
$(go env GOPATH)/bin/kind create cluster --name kind-1
確認
$ kind get clusters kind-1
-
kubectl の導入
KINDを使う分には kubectl が必須でないようで、必要なら導入すする。
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -m 755 kubectl /usr/local/bin/kubectl rm kubectl
MetalLBのインストール
-
インストール
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
-
アドレスプールの設定
次の注意点がある
- MetalLB v0.13 で非互換があり、configMapの指定は認識されない
- kindで使用しているの docker のIPを指定しないと、IPの割り当てが成功しても通信ができない
Dockerのネットワーク上のIPを調べる
$ docker network inspect kind --format '{{json (index .IPAM.Config 0).Subnet}}' "172.18.0.0/16"
metallb-IPAddressPool.yaml
apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: first-pool namespace: metallb-system spec: addresses: - 172.18.254.240/28 # ★環境ごとに書き換える autoAssign: true # ★true: 自動割り当て, false: 手動割り当て(loadBalancerIPで指定) --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: l2-ad namespace: metallb-system spec: ipAddressPools: - first-pool
-
デプロイ
$ kubectl apply -f metallb-IPAddressPool.yaml ipaddresspool.metallb.io/first-pool created l2advertisement.metallb.io/l2-ad created
上記の手順については次のドキュメントにあるが、2022-08-28現在、ConfigMapを使う例となっており、IPAddressPoolとして読み替えが必要。
Goアプリケーションのデプロイ
/api/greet/John に応答するだけのアプリ。
main.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"strings"
)
func greetHandler(w http.ResponseWriter, r *http.Request) {
sub := strings.TrimPrefix(r.URL.Path, "/api/greet")
_, name := filepath.Split(sub)
var m any
if name != "" {
m = map[string]interface{}{
"message": fmt.Sprintf("Hello, %s!", name),
}
} else {
m = map[string]interface{}{
"status": 404,
}
}
resJson, err := json.Marshal(m)
if err != nil {
panic(err.Error())
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, "%s", string(resJson))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/greet/", greetHandler)
http.ListenAndServe(":8080", mux)
}
Dockerfile
容量を削減する目的でマルチステージビルドを使用している。
FROM golang:1.19-alpine3.16 as builder
COPY ./src/main.go ./
RUN go build -o /go-app ./main.go
FROM alpine:3.16 as runtime
EXPOSE 8080
COPY --from=builder /go-app .
ENTRYPOINT ["./go-app"]
Deploy
-
Build a image
docker image build --tag greet-go:0.1 .
-
KIND にイメージをPushする
kind --name kind-1 load docker-image greet-go:0.1
push確認
go-hello-app が一覧に含まれることが確認できる。$ docker container ls --format "table {{.Image}}\t{{.State}}\t{{.Names}}" IMAGE STATE NAMES kindest/node:v1.25.3 running kind-1-control-plane $ docker exec -it kind-1-control-plane crictl images IMAGE TAG IMAGE ID SIZE docker.io/kindest/kindnetd v20221004-44d545d1 d6e3e26021b60 25.8MB docker.io/kindest/local-path-helper v20220607-9a4d8d2a d2f902e939cc3 2.86MB docker.io/kindest/local-path-provisioner v0.0.22-kind.0 4c1e997385b8f 17.4MB docker.io/library/greet-go 0.1 abd18c430d5ec 12.5MB quay.io/metallb/controller v0.13.7 e73361dabfb86 25.6MB quay.io/metallb/speaker v0.13.7 738c5d221d601 46.9MB registry.k8s.io/coredns/coredns v1.9.3 5185b96f0becf 14.8MB registry.k8s.io/etcd 3.5.4-0 a8a176a5d5d69 102MB registry.k8s.io/kube-apiserver v1.25.3 4bc1b1e750e34 76.5MB registry.k8s.io/kube-controller-manager v1.25.3 580dca99efc3b 64.5MB registry.k8s.io/kube-proxy v1.25.3 86063cd68dfc9 63.3MB registry.k8s.io/kube-scheduler v1.25.3 5225724a11400 51.9MB registry.k8s.io/pause 3.7 221177c6082a8 311kB
-
KIND にデプロイする
今回はイメージがローカルにしかないので
imagePullPolicy
にNever
を設定する必要があることに注意する。greet-go.deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: greet-go-app spec: selector: matchLabels: app: greet-go-app replicas: 2 template: metadata: labels: app: greet-go-app spec: containers: - name: greet-go image: greet-go:0.1 imagePullPolicy: Never ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: greet-go-service spec: type: LoadBalancer selector: app: greet-go-app ports: - name: http port: 3000 targetPort: 8080 protocol: TCP
デプロイ
$ kubectl apply -f greet-go.deployment.yaml deployment.apps/greet-go-app created service/greet-go-service created
動作確認
Pod IPに対して直接通信する
Pod がデプロイできていれば行える確認。Serviceがなくとも動く。
-
Pod のIPを確認する
$ kubectl get pods -l app=greet-go-app -o custom-columns="Pod IP":.status.podIP,"Container port":.spec.containers[0].ports[].containerPort Pod IP Container port 10.244.0.7 8080 10.244.0.6 8080
-
Control plane から curl を叩く
$ docker ps --format "table {{.Names}}\t{{.Image}}" --filter name=kind NAMES IMAGE kind-1-control-plane kindest/node:v1.25.3 $ docker container exec -it kind-1-control-plane curl -s http://10.244.0.7:8080/api/greet/John | python3 -m json.tool { "message": "Hello, John!" }
-
Internal DNSでPodに通信する
以下は Service で2つのPodのいずれかの名前解決を行う。
この例では Pod側のポートは 8080 だが Service側のポートは 3000 であることに注意する(この辺りの理解がごちゃ混ぜにならないように敢えて変えている)。$ kubectl get services greet-go-service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE greet-go-service LoadBalancer 10.96.94.74 172.18.254.240 3000:30485/TCP 65m $ docker container exec -it kind-1-control-plane curl -s http://greet-go-service.default.svc.cluster.local:3000/api/greet/John | python3 -m json.tool curl: (6) Could not resolve host: greet-go-service.default.svc.cluster.local $ kubectl run -q -n default -it curl --image=curlimages/curl --rm --restart=Never --wait=true -- -s -L http://greet-go-service.default.svc.cluster.local:3000/api/greet/John | python3 -m json.tool { "message": "Hello, John!" }
補足
-
kubectl run
-
-q
はpod "curl" deleted
などのkubectl run
の出力を削るため -
--wait=true
は特定の警告メッセージを出すのを抑制する
kubectl run
が即終わってしまうと次のメッセージが出るwarning: couldn't attach to pod/curl, falling back to streaming logs: unable to upgrade connection: container curl not found in pod curl_default
-
-
ポート転送して確認する
-
Pod name を確認する
$ kubectl get pods -l app=greet-go-app NAME READY STATUS RESTARTS AGE greet-go-app-78cc5cfdd8-4q6lt 1/1 Running 0 13m greet-go-app-78cc5cfdd8-kdpdl 1/1 Running 0 13m
-
別の端末でポート転送を行う
下記は、Ctrl-Cを打つまで端末を占有するのでWSL内の別端末で確認する。以下のように実行できた。$ POD_NAME="greet-go-app-78cc5cfdd8-4q6lt" $ kubectl port-forward $POD_NAME 3000:8080 Forwarding from 127.0.0.1:3000 -> 8080 Forwarding from [::1]:3000 -> 8080 ...(注:Ctrl-Cを打つまで復帰しない)
-
Curlでレスポンスを確認する
$ curl -s http://localhost:3000/api/greet/John \ --header "Content-Type: application/json" \ --request "GET" | python3 -m json.tool { "message": "Hello, John!" }
External IP経由の確認
この確認が本命となる。
-
EXTERNAL-IP の確認をする
うまくいっていれば次のように、EXTERNAL-IPを取得できる$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE greet-go-service LoadBalancer 10.96.94.74 172.18.254.240 3000:30485/TCP 17m kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 65
-
Curlでレスポンスを確認する
$ curl -s http://172.18.254.240:3000/api/greet/John \ --header "Content-Type: application/json" \ --request "GET" | python3 -m json.tool { "message": "Hello, John!" }
なお、この時点でWindows 10のホスト側からの通信は失敗。多分、追加の設定が必要。
補足: トラブルシューティング
いくつかはまった点を書いておく。
curl で Failed to connect to 172.18.xxx.yyy port 8080: No route to host と怒られる
この記事は Deployment を使った記事で、試しに Pod で作ろうとしたときに発生した。
状況
-
EXTERNAL-IP
は取得できている - arp コマンドの結果、
HWaddress
が(incomplete)
になっている -
sudo arping 172.18.xxx.yyy -c 3
ですべてタイムアウトする
原因
結果的には、selector
で Pod が見つけられない状態にあった。
labels に app: ~
を追加し問題は解消した。
-
go-hello-app.pod.yaml
apiVersion: v1 kind: Pod metadata: name: go-hello-pod labels: # 追加 app: go-hello-app # 追加 spec: containers: - name: go-hello-app image: greet-go:0.1 imagePullPolicy: Never ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: go-hello-service spec: type: LoadBalancer selector: app: go-hello-pod # pod の labelsを見ている ports: - name: http port: 3000 targetPort: 8080 protocol: TCP
OS再帰動詞、WSLを再開後に IPAddressPool の更新ができなくなる
原因
- metallb の Controller が Unhelthy になっている
暫定対処
- kind のクラスタを再作成したらうまくいった
参考
-
MetalLB
-
その他