こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の中村です。
Kubernetesの機能や関連するOSSをローカルPCで試す時、minikubeやkindを利用されている方も多いと思います。
KubernetesのIngress周りの機能や関連するOSSを、ローカルPC(Mac)で試す場合、Public Cloudとは別の設定(Service typeをLoadBalancerではなくNodePort)にしたり、hostsファイルを書き換えたり、使われている方も多いのではないでしょうか。
また、複数のWorkerノードが動作しているKubernetesクラスタで試したい場合、minikubeでは機能不足で実現できないし、VMを立てて構築するにもリソースが足らないという場合に候補になるのが、kind(Kubernetes in Docker)です。
本稿では、kindを使ったローカルKubernetesクラスタ環境で、なるべく同じようなIngress周りの設定でデプロイできるようにする方法をご紹介したいと思います。
システム構成
今回利用したOS、各OSSのVersionは以下の通りです。
分類 | コンポーネント | Version |
---|---|---|
OS | MacOS | Catalina(10.15.1) |
OSS | Docker for Mac | 2.1.0.5 |
OSS | kind | v0.6.0 |
OSS | etcd | 3.4.3 |
OSS | CoreDNS | 1.6.2 |
OSS | Kubernetes | 1.16.3 |
OSS | kubectl | 1.16.3 |
OSS | MetalLB | 0.8.3 |
OSS | ExternalDNS | 0.5.17 |
注意! (2020/2/12追記)
Docker for Macが2.2の場合、localhost(127.0.0.1)以外のIPアドレスでのpublishができなくなる問題が発生しているようです。
今のところ、2.2での解決策が無いようですので、2.1.0.5を使って進めてください。
また、構築するシステム構成は以下の図ような構成です。
CoreDNS/etcdのインストールと設定
Homebrewを使って、CoreDNS/etcdをMac上にインストールします。
$ brew tap "coredns/deployment" "https://github.com/coredns/deployment"
$ brew install coredns etcd
デフォルトではetcdを参照しないため、特定のドメイン(ここではexample.com)のDNSレコードをetcdを参照するようにCoreDNSの設定を書き換えます。以下のファイルを編集します。
$ sudo vim /usr/local/etc/coredns/Corefile
以下のようにCorefileを書き換えます。
example.com { // ←追加
etcd { // ←追加
path /skydns // ←追加
} // ←追加
cache // ←追加
} // ←追加
. {
fallthrough
}
・・・・
そして、CoreDNS/etcdのサービスを起動します。
$ brew services start etcd
$ brew services start coredns
CoreDNSが起動しているかチェックします。
$ dig www.google.com +short @localhost
172.217.6.36
正しく動いているようであれば、システム設定>ネットワーク>拡張のDNSタブからDNSサーバに127.0.0.1を手動でDNSサーバを登録します。
socatを用いてホストからKubernetesクラスタ内へフォワード設定
socatコンテナをDocker for Mac上で動作させて、ホストネットワークからKubernetesクラスタ内に通信できるようにします。ただし、この後セットアップするMetalLBで管理されるIPアドレスでアクセスがきたら、Kubernetesクラスタ内にフォワードさせたいので、まずはループバックデバイスにエリアス設定をします。
$ sudo ifconfig lo0 alias 172.17.255.1 netmask 0xffffff00
$ sudo ifconfig lo0 alias 172.17.255.2 netmask 0xffffff00
エリアス設定ができたらKubernetesクラスタ内に転送するsocatコンテナを起動します。なお、転送するIPアドレス、ポートの組合せ毎に立てる必要がありますので、80, 443ポートの2つを、2つのIPアドレスにフォワードしたい場合は、以下のように合計2つのsocatコンテナを起動します。
$ docker run -d --name kind-proxy-lb1-80 --publish 172.17.255.1:80:80 alpine/socat -dd tcp-listen:80,fork,reuseaddr tcp-connect:172.17.255.1:80
$ docker run -d --name kind-proxy-lb1-443 --publish 172.17.255.1:80:80 alpine/socat -dd tcp-listen:80,fork,reuseaddr tcp-connect:172.17.255.1:443
$ docker run -d --name kind-proxy-lb2-80 --publish 172.17.255.1:80:80 alpine/socat -dd tcp-listen:80,fork,reuseaddr tcp-connect:172.17.255.2:80
$ docker run -d --name kind-proxy-lb2-443 --publish 172.17.255.1:80:80 alpine/socat -dd tcp-listen:80,fork,reuseaddr tcp-connect:172.17.255.2:443
dockerコマンドで起動されているか確認します。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bab6583aa9a1 alpine/socat "socat -dd tcp-liste…" 2 minutes ago Up 2 minutes 172.17.255.2:443->443/tcp kind-proxy-lb2-443
b900450fe363 alpine/socat "socat -dd tcp-liste…" 2 minutes ago Up 2 minutes 172.17.255.2:80->80/tcp kind-proxy-lb2-80
3a281fb45acd alpine/socat "socat -dd tcp-liste…" 2 minutes ago Up 2 minutes 172.17.255.1:443->443/tcp kind-proxy-lb1-443
9e4b5ae42302 alpine/socat "socat -dd tcp-liste…" 2 minutes ago Up 2 minutes 172.17.255.1:80->80/tcp kind-proxy-lb1-80
Kubernetesクラスタを構築
次に、kindでKubernetesクラスタを構築します。
まずは、kindをこちらを参考にインストールします。Macの場合はHomebrewで簡単にインストールできます。
$ brew install kind
kindコマンドがインストールされたら直ぐにKubernetesクラスタが構築できるのですが、折角なので、Controlノードが1つWorkerノードが3つのクラスタを構築するために、以下のようなconfig.yamlを作成します。
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
もし、Feature Gatesを設定したい場合は、こちらを参考にして上記のconfig.yamlを修正してください。
config.yamlが準備できたら、以下のコマンドを実行します。
$ kind create cluster --name kind --config config.yaml
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.16.3) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
これでKubernetesクラスタが構築できました。実際に確認してみましょう。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready master 2m28s v1.16.3
kind-worker Ready <none> 112s v1.16.3
kind-worker2 Ready <none> 113s v1.16.3
kind-worker3 Ready <none> 113s v1.16.3
config.yamlでの設定の通り、Controlノードが1つ、Workerノードが3つのKubernetesクラスタができました。
MetalLBをデプロイ
先程構築したKubernetesクラスタにMetalLBをデプロイします。詳細はこちらを参考にして頂けると良いと思います。(この記事ではMacの場合、kubectl proxyを推奨していますが、socatを使ってdocker network "bridge"内にアクセスできるようにしているので、kubectl proxyなしでアクセスできます)
まずはMetalLBをKubernetesクラスタ上にデプロイします。実行するコマンドは以下の通りです。
$ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.3/manifests/metallb.yaml
現時点でのMetalLBの最新Versionは0.8.3なのでそれを利用しています。
次に、L2モードで動作するような以下のようなConfigMapを作成します。割り当てるIPアドレスは、Docker networkの"bridge"ネットワークと同じネットワーク内のIPアドレスである172.17.255.1-172.17.255.2にします。これは、ループバックデバイスにaliasを設定し、socatでフォワーディング設定しているIPアドレスと一致する必要がありますので、変更している場合は適宜、こちらも変更してください。
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 172.17.255.1-172.17.255.2
そして、以下のコマンドでKubernetesクラスタにデプロイします。
$ kubectl apply -f metallb-config.yaml
ExternalDNSをデプロイ
次にExternalDNSをデプロイします。
以下のようなmanifestファイルを作成して、Kubernetesクラスタにデプロイします。
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: kube-system
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --provider=coredns
- --log-level=debug # debug only
- --policy=sync
- --domain-filter=example.com
env:
- name: ETCD_URLS
value: http://host.docker.internal:2379
今回は、Mac上で動作しているCoreDNS + etcdをDNSサーバとして利用するので、最後の方に設定されている、providerオプション及び、環境変数ETCD_URLSの設定は忘れないでください。
また、"example.com"というドメインでWebサービスをデプロイする想定で、ExternalDNSのargsにdomain filterの設定をしていますが、適宜変更してください。
以上で環境構築は完了です。
NGINXをServiceとしてデプロイして動作確認
環境構築ができたら、実際にNGINXをサービスとしてデプロイして動作確認してみましょう。ExternalDNSで自動でDNSレコードがCoreDNSに設定されて、設定したhost名でアクセスできるか確認していきたいと思います。
まずは、NGINXをサービスとしてデプロイするために、以下のようなmanifestファイルを作成し、Kubernetesクラスタにデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: "nginx.example.com"
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
Serviceリソースを直接できるようにDNSレコードをExternalDNSに登録させるには、typeをLoadBalancerとし、annotationに"external-dns.alpha.kubernetes.io/hostname: <hostname>"という形で設定する必要があります。
デプロイできたか確認していきましょう。
まずは、NGINXのPodが作成され、コンテナが起動しているか確認します。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-5f78746595-jljzl 1/1 Running 0 10s
もし、StatusがRunningになっていなければ、Runningになるまで待ちましょう。コンテナが起動していることが確認できたら、MetalLBによって、デプロイしたServiceにExternal IPが割り当てられたか確認します。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11m
nginx LoadBalancer 10.104.22.203 172.17.255.1 80:31998/TCP 3m15s
External IPが172.17.255.1が割り当てられているようです。
本当にアクセスできるかcurlを使って確認してみましょう。
$ curl http://172.17.255.1
<!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>
NGINXから応答が返ってきているようです。
次に、ExternalDNSがデプロイされたServiceを検知して、DNSレコードを作成したかログで確認してみましょう。
$ kubectl -n kube-system logs external-dns-56c79c6b69-ptzsr
〜〜〜
time="2019-12-17T18:57:05Z" level=debug msg="service added"
time="2019-12-17T18:57:06Z" level=debug msg="No endpoints could be generated from service kube-system/kube-dns"
time="2019-12-17T18:57:06Z" level=debug msg="Endpoints generated from service: default/nginx: [nginx.example.com 0 IN A 172.17.255.1 []]"
time="2019-12-17T18:57:06Z" level=debug msg="No endpoints could be generated from service default/kubernetes"
time="2019-12-17T18:57:06Z" level=info msg="Add/set key /skydns/com/example/nginx/3ce624fa to Host=172.17.255.1, Text=\"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/nginx\", TTL=0"
〜〜〜
"nginx.example.com"というDNSレコードをCoreDNSのバックエンドのetcdに登録されたようです。
なお、ExternalDNSは1分おきにリソースの追加・削除をチェックして、そのタイミングでDNSレコードの追加・削除を行います。もし上記のようなログが出ていなかったら、1分程度待ってみてください。
Mac上のCoreDNSで名前解決できるか確認してみましょう。
$ dig nginx.example.com @localhost
; <<>> DiG 9.10.6 <<>> nginx.example.com @localhost
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17040
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx.example.com. IN A
;; ANSWER SECTION:
nginx.example.com. 300 IN A 172.17.255.1
;; Query time: 41 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Dec 17 11:09:21 PST 2019
;; MSG SIZE rcvd: 79
Aレコードとして登録されていることが確認できました。
それでは、実際にcurlでアクセスしてみましょう。
$ curl http://nginx.example.com
<!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>
次にブラウザからアクセスできるか確認してみましょう。
ブラウザのアドレスバーに"http://nginx.example.com"というURLを入力します。
ブラウザからもアクセスできました。
まとめ
以上のように、ローカルマシン(Mac)上で、kindを用いてKubernetesクラスタを構築し、socatコンテナを使って、ローカルマシンからKubernetesクラスタ内にアクセスできるようにできました。そして、ローカルマシン上で動作させたCoreDNS/etcdと、ExternalDNSを連携させることで、ローカルマシン上でDNSでのKubernetesクラスタ上にデプロイしたサービスにアクセスできるようになりました。
本稿では触れていませんが、これを発展させることで、ローカルマシン上でDNSで振り分けるIngress Controllerのテストなども簡単にできると思います。
これで、Kubernetesを色々試してみてはいかがでしょうか。