130
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kubernetes の Service type LoadBalancer を手作業で作る

Last updated at Posted at 2018-02-23

ベアメタルで Kubernetes を運用している皆さん、外部からどうやって Kubernetes 上の Service に接続しようかと困っていませんか?具体的にいうと Service type LoadBalancer が無いので困っていませんか?

Kubernetes の Service type LoadBalancer に対応していないインフラストラクチャ上で type LoadBalancer な Service を作成するとこんな感じで、

$ kubectl get svc
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx     LoadBalancer   10.254.0.220   <pending>     80:30692/TCP   11s

いつまでたっても EXTERNAL-IP が <pending> のままでロードバランサーが作られることはありません。というのも Kubernetes の中でロードバランサーを作成し、Service の EXTERNAL-IP をロードバランサーの IP アドレスに設定する、という処理を担当する controller が動いていないからです。(いないから。

そこで今回のこの記事の趣旨は、いないなら手作業でやってしまえば良いじゃない!です。手順は以下。

  1. 前準備
  2. Pod/Service の作成
  3. ロードバランサーの作成
  4. Service へロードバランサーの情報を設定

前準備

とりあえずテスト用に lbtest という名前の Namespace と、後々の作業のために lbtest 上の default サービスアカウントに cluster-admin 権限を与えておきます。

$ cat << EOF | kubectl create -f -
---
kind: Namespace
apiVersion: v1
metadata:
  name: lbtest
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: lbtest
subjects:
- kind: ServiceAccount
  namespace: lbtest
  name: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
EOF

Pod/Service の作成

テスト用に nginx が動いている Pod と、その Pod にアクセスするための、type: LoadBalancerService を作成します。

$ cat << EOF | kubectl create -f -
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: lbtest
  labels:
    app: nginx
spec:
  containers:
    - name: nginx-container
      image: nginx
      ports:
      - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx
  namespace: lbtest
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer
EOF

作ったサービスを確認すると、案の定 EXTERNAL-IP が <pending> のままですね。

$ kubectl get svc -n lbtest
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx     LoadBalancer   10.254.0.220   <pending>     80:30692/TCP   11s

ロードバランサーの作成

ちなみに、Kubernetes の type: LoadBalncerService のロードバランサーがどうやってPod に対してロードバランシングを行なっているかというと、
一番簡単な説明が Tim Hockin が Google Cloud Next '17 で行なった The ins and outs of networking in Google Container Engine and Kubernetesというプレゼンを見るのが早いのだけれども、すごい大雑把にいうとこんな感じ。

Service type LoadBalancer

つまりロードバランサーは ServiceNodePort にバランシングを行えば良いということになる。先ほど作った Service30692NodePort で待ち受けているようなので、、

Node の IP Address を確認し、

$ kubectl get nodes
NAME             STATUS    ROLES     AGE       VERSION
172.18.201.121   Ready     <none>    23d       v1.9.2
172.18.201.122   Ready     <none>    23d       v1.9.2
172.18.201.123   Ready     <none>    23d       v1.9.2

以下のような HAProxy の設定を書いて HAProxy を起動する。

$ cat << EOF > haproxy.cfg
global
    maxconn 256

defaults
    mode http
    timeout client     120000ms
    timeout server     120000ms
    timeout connect      6000ms

listen http-in
    bind *:80
    server server1 172.18.201.121:30692
    server server2 172.18.201.122:30692
    server server3 172.18.201.123:30692
EOF
$ docker run -d --name haproxy \
  -p 80:80 \
  -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
  haproxy:1.8

localhost に対して curl を叩いて見ると、ロードバランサーがちゃんと動作していることがわかります。

$ curl 127.0.0.1:80                                                                                                      (cluster/lbtest)
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Service へロードバランサーの情報を設定

さて、細かいことを気にしなければ(笑) これで Kubernetes の Service に対して外部のロードバランサーが設定できたことになるのですが、

$ kubectl get svc -n lbtest
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx     LoadBalancer   10.254.0.220   <pending>     80:30692/TCP   2h

やっぱり Service の EXTERNAL-IP が <pending> になったままになっているのが気になる人が出てくる気もします。ので、ここを修正します。

まず、lbtest Namespace の default サービスアカウントのアクセストークンをどうにかしてとってきます。

$ TOKEN=$(kubectl describe secret \
  $(kubectl get secrets | grep default | cut -f1 -d ' ') | \
  grep -E '^token' | \
  cut -f2 -d':' | tr -d '\t' | tr -d ' ')

このアクセストークンが有効かどうかちょっと確認してみましょう。

$ curl -k https://${KUBERNETES_API_ENDPOINT}/api/v1/namespaces \
  --header "Authorization: Bearer $TOKEN"
{
  "kind": "NamespaceList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/namespaces",
    "resourceVersion": "3765645"
... (中略)
}%

良さそうです。

ちょっと調べたところ、Kubernetes の Service のステータス (EXTERNAL-IP の情報を含んでいる属性)の情報を書き換えるには単純に Service の該当属性を書き換えるだけではダメらしく、service/status サブリソースを書き換える必要があるようです。

とりあえず現状の service/status をとってきます。

$ curl -k --header "Authorization: Bearer $TOKEN" \
  https://${KUBERNETES_API_ENDPOINT}/api/v1/namespaces/lbtest/services/nginx/status \
  > nginx-status.json

そしてとってきた情報の中の、/status/loadBalancer の項目を修正します。

$ diff -u nginx-status.json.old nginx-status.json
--- nginx-status.json.old	2018-02-23 14:01:55.000000000 +0900
+++ nginx-status.json	2018-02-23 14:01:44.000000000 +0900
@@ -31,7 +31,7 @@
   },
   "status": {
     "loadBalancer": {
-
+      "ingress": [ { "ip": "127.0.0.1" } ]
     }
   }
 }

ここの ip にはロードバランサーのアドレスを設定します。それではこの情報を使って Service を更新しましょう!

$ curl -k --header "Authorization: Bearer $TOKEN" \
  https://${KUBERNETES_API_ENDPOINT}/api/v1/namespaces/lbtest/services/nginx/status \
  -X PUT -d @nginx-status.json -H 'content-type:application/json'

これで Service の情報は更新されました。

$ kubectl get svc -n lbtest
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx     LoadBalancer   10.254.0.220   127.0.0.1     80:30692/TCP   2h

ちゃんと更新されてますね、素晴らしい!これであなたのベアメタルなKubernetesクラスターでも何も気にすることなく Service type: LoadBalancer を使いたい放題ですね!やった!

補足

この記事は MetalLB のソースコードを読んで冗談で書いたものです。今までは特に詳細を気にすることなく、CloudProvider インタフェースの LoadBalancer() を実装しなくちゃいけない (must) なのかと思ってましたがそんなことなかったんですね。ちゃんと確認するもんです。

補足2

上記を自動化すれば一応使い物になる Service type: LoadBalancer controller ができるかも?

130
75
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
130
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?