😊

Oracle Cloud で実験的な Kubernetes クラスタを無料で構築する方法

2023/04/11に公開約10,000字

Oracle Cloud には非常に寛容な無料枠があり、たとえば次のような構成の Kubernetes クラスタを構築できます。

  • コントロールプレーン: 2 COPU & メモリ 12 GB
  • ワーカーノード1: 1 OCPU & メモリ 6 GB
  • ワーカーノード2: 1 OCPU & メモリ 6 GB

このスペックはドキュメントの次の部分から来ています。

すべてのテナンシは、Armプロセッサを含むVM.Standard.A1.Flexシェイプを使用するVMインスタンスに対して、月ごとに最初の3,000 OCPU時間および18,000 GB時間を無料で取得します。Always Free テナンシの場合、これは4 OCPUおよび24 GBのメモリーと同等です

この記事では実験的な Kubernetes クラスタを Oracle Cloud に構築する手順について扱います。なお、先行事例はすでにいくつかあり、僕自身も「Oracle Cloudの無料枠でKubernetesクラスタを構築する(完全版) | blog.potproject.net」を参考にしました。特に iptables の設定はそのまま使用しています。主な違いは次の通りです。

  1. シェルスクリプトによる環境構築の自動化
  2. Oracle Cloud の「仮想クラウド・ネットワーク」の設定

それでは具体的な構築方法に進みましょう。

Oracle Cloud でのインスタンス作成とセキュリティリストの設定

Oracle Cloud にはログインできているとします。「コンピュート・インスタンスの作成」からインスタンスを作成します。ほとんど困ることはないと思いますが「イメージとシェイプ」だけは次のようにする必要があります。

「Canonical Ubuntu 20.04」は Kubernetes を 20.04 で動かしたいからで、A1.Flex は Oracle Cloud を無料で使うためです。

また、イングレス・ルールに次の許可を追加します。これらは Kubernetes ノード同士が通信をするために必要です。

iptables への設定追加

Kuberentes ノード間で通信できるように iptables の設定を変更します。

Oracle Cloud で Ubuntu 20.04 を使用している場合、この Gist の内容/etc/iptables/rules.v4 を置き換え、次のコマンドで iptables を更新します。

sudo iptables-restore < /etc/iptables/rules.v4

Oracle Cloud でシェルスクリプトを実行

コントロールプレーンとワーカーノードで別々のシェルスクリプトを実行します。

これらのスクリプトは、元々 Kim Wuestkamp さんが Udemy で開催している CKS 学習者向けコース配布しているものです。これらは GCP での環境構築を目的としていますが、ここでは Oracle Cloud で Arm プロセッサーのインスタンスを使用するため、少し変更を加えています。

具体的には containerd を Arm 向けのファイルに変更しています。つまり containerd-1.6.12-linux-amd64.tar.gzcontainerd-1.6.12-linux-arm64.tar.gz に書き換えています。

まず、コントロールプレーンでシェルスクリプトを実行します。このシェルスクリプトは最後にワーカーノードが Kubernetes クラスターに参加する際に必要なコマンドを出力します。メモしておきましょう。

$ sudo -i
# bash install_master.sh
...
### COMMAND TO ADD A WORKER NODE ###
kubeadm join 10.0.0.162:6443 --token xxxxxx.xxxxxxxxxxxxxxxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

次に、ワーカーノードでシェルスクリプトを実行します。その後、コントロールプレーンでのシェルスクリプト実行時に得たトークンを使い、次のようにコマンドを実行します。

$ sudo -i
# bash install_worker.sh
...

# kubeadm join 10.0.0.162:6443 --token xxxxxx.xxxxxxxxxxxxxxxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

ここまでの設定を終えると、コントロールプレーンで kubectl get node などを実行してワーカーノードを含む Kubernetes クラスタができていることを確認できます。

# kubectl get node
NAME    STATUS   ROLES           AGE     VERSION
node1   Ready    control-plane   8m45s   v1.26.1
node2   Ready    <none>          77s     v1.26.1

DNS の設定

まだ Pod 同士が通信をするためには問題があります。たとえば frontend Pod と backend Pod が default ネームスペースに存在するとき、次のコマンドは失敗します。

$ kubectl exec backend -- curl frontend
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:19 --:--:--     0
curl: (6) Could not resolve host: frontend
command terminated with exit code 6

frontend という名前が解決できていないことから CoreDNS の問題ではないかとあたりをつけ、次のコマンドを実行するとエラーを確認できます。

$ kubectl logs -n kube-system -l k8s-app=kube-dns
...
[ERROR] plugin/errors: 2 3656038324237740050.970672069479610665. HINFO: read udp 192.168.0.4:34296->169.254.169.254:53: read: no route to host
[ERROR] plugin/errors: 2 3656038324237740050.970672069479610665. HINFO: read udp 192.168.0.4:56195->169.254.169.254:53: read: no route to host

ここで 169.254.169.254:53CoreDNS のリクエスト先であり、これは Oracle Cloud のリゾルバエンドポイントです

CoreDNS の ConfigMap を確認します。

CoreDNS の ConfigMap
$ kubectl get configmap -n kube-system coredns -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2023-04-11T13:55:15Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "218"
  uid: 31ccd15e-3073-4af3-92e7-57ad21596c7d

次の箇所からクラスターノードの /etc/resolv.conf を参照していることを確認しました。

forward . /etc/resolv.conf {
   max_concurrent 1000
}

次にクラスターノードの /etc/resolv.conf の中身を確認します。

/etc/resolv.conf
# This file is managed by man:systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0 trust-ad
search vcn04192235.oraclevcn.com

いわく、/etc/resolv.conf は自動で生成されたものなので "uplink DNS server" の中身は resolvectl status で確認せよ、とのことでした。

`resolvectl status` の結果
Global
       LLMNR setting: no
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no
          DNSSEC NTA: 10.in-addr.arpa
                      16.172.in-addr.arpa
                      168.192.in-addr.arpa
                      17.172.in-addr.arpa
                      18.172.in-addr.arpa
                      19.172.in-addr.arpa
                      20.172.in-addr.arpa
                      21.172.in-addr.arpa
                      22.172.in-addr.arpa
                      23.172.in-addr.arpa
                      24.172.in-addr.arpa
                      25.172.in-addr.arpa
                      26.172.in-addr.arpa
                      27.172.in-addr.arpa
                      28.172.in-addr.arpa
                      29.172.in-addr.arpa
                      30.172.in-addr.arpa
                      31.172.in-addr.arpa
                      corp
                      d.f.ip6.arpa
                      home
                      internal
                      intranet
                      lan
                      local
                      private
                      test

Link 14 (calia622272a21e)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 13 (cali0d901d3d613)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 12 (cali1e9261921dc)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 11 (cali9b723216629)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 6 (flannel.1)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 3 (docker0)
      Current Scopes: none
DefaultRoute setting: no
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no

Link 2 (enp0s6)
      Current Scopes: DNS
DefaultRoute setting: yes
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no
  Current DNS Server: 169.254.169.254
         DNS Servers: 169.254.169.254
          DNS Domain: vcn04192235.oraclevcn.com

コマンドの出力で「Global セクションに DNS Server: 169.254.169.254 が存在しないのはおかしい」と思い、/etc/systemd/resolved.conf に次の内容を追記しました。

[Resolve]
DNS=169.254.169.254

その後 systemctl restart systemd-resolved を実行したところ、GlobalDNS Server: 169.254.169.254 が追加され、Pod 同士のコミュニケーションも取れるようになりました。

残る謎

そもそも kube-dns のログでは 169.254.169.254:53 に到達できないというエラーでした。しかし、ここではクラスターノードの DNS Server に 169.254.169.254 を追加しただけで、そこへの到達性について手を加えていないのに、通信できてしまいました。

クラスタを再度作るときにはもう少し調べてみようと思っています。

GitHubで編集を提案

Discussion

ログインするとコメントできます