k3s実験

k3sの「単一バイナリの軽量ディストリビューション」という説明を実際に手を動かして確認するための実験記録。ホストにsystemdサービスとして直接インストールする公式手順(get.k3s.io)は、iptables変更やsystemdユニット登録を伴い後片付けが面倒なため、代わりに公式Dockerイメージrancher/k3sを単体コンテナとして動かす方式を採った。 #kubernetes

使ったもの

  • Docker 29.1.3(既にインストール済み)
  • rancher/k3s:v1.36.3-k3s1イメージ(2026年8月時点の最新安定版)
  • kubectlはホストにインストールせず、イメージに同梱されているものをdocker exec経由で使った

動かし方

docker run --privileged --name k3s-experiment \
  --hostname k3s-experiment -p 6443:6443 \
  -d rancher/k3s:v1.36.3-k3s1 server

--privilegedが必要な理由: k3sはコンテナの中でさらにcontainerdを動かし、その上でPod用のコンテナ(ネストしたコンテナ)を起動するため。単なるdocker runの権限では足りない。

ノードのReady化はかなり速く、起動から15秒ほどでkubectl get nodesReadyを返した。一方でTraefikはHelmChart経由のインストール(helm-install-traefikという一時的なJobがHelmチャートを展開する)のため、全PodRunningになるまでは40〜50秒ほどかかった。

躓いた点

  • コンテナのIPを取ろうとdocker inspect -f '{{.NetworkSettings.IPAddress}}' k3s-experimentを打ったらmap has no entry for key "IPAddress"で失敗した。今のDockerではこのフィールドが常に空で、docker inspect -f '{{json .NetworkSettings.Networks}}'のようにNetworks.<network名>.IPAddressを見に行く必要があった(このホストではbridgeネットワークで172.17.0.4)。
  • ポートは-p 6443:6443しか公開していないが、kubectl expose --type=NodePortで払い出されたポートやTraefikのServiceLBが使うポートには、コンテナのIP(172.17.0.4)に直接アクセスすれば(Docker bridgeネットワークはホストから素通しなので)届く。ホスト側で-pを追加しなくても動作確認できた。

実行結果

単一バイナリ・単一プロセスであることの確認

$ docker exec k3s-experiment ps aux
PID   USER     COMMAND
    1 root     /bin/k3s init
   56 root     {exe} k3s server
   81 root     containerd
  ...
 1485 65532    /coredns -conf /etc/coredns/Corefile
 1694 1000     /metrics-server ...
 1812 root     local-path-provisioner start --config /etc/config/config.json
 2605 65532    traefik traefik --entryPoints.web.address=:8000/tcp ...

k3sノートに書いた「コントロールプレーン全コンポーネントを単一プロセスに封じ込める」というのは文字通りで、PID 56のk3s server1プロセスがapiserver・scheduler・controller-manager・kubeletをまとめて担っている。ただしcontainerdは別プロセス(PID 81)であり、CoreDNS・metrics-server・local-path-provisioner・Traefikも「k3sバイナリの中の機能」ではなく、containerdが起動する普通のPod=普通のコンテナとして動いている。つまり「同梱」の実態は「1バイナリに静的リンクされている」のではなく、「起動時に自動でmanifest/HelmChartがapplyされ、Pod化されて動く」という意味だとわかった。

データストア(sqlite3 + kine)の確認

$ docker exec k3s-experiment ls -la /var/lib/rancher/k3s/server/db/
-rw------- 1 root root 9273344 state.db
-rw------- 1 root root   32768 state.db-shm
-rw------- 1 root root 6105872 state.db-wal

k3sノートで触れた「デフォルトデータストアはetcdではなくsqlite3(kine経由)」の通り、state.dbという素のsqliteファイルとしてクラスタ状態が保存されていた。WALモード(-shm/-wal)で運用されている。

hello world: nginxを実際にデプロイして疎通確認

docker exec k3s-experiment kubectl create deployment hello-nginx --image=nginx
docker exec k3s-experiment kubectl expose deployment hello-nginx --port=80 --type=NodePort

払い出されたNodePort(この実行では30126)に、コンテナIP経由で直接アクセス:

$ curl -s -i http://172.17.0.4:30126/
HTTP/1.1 200 OK
Server: nginx/1.31.3
...
<title>Welcome to nginx!</title>

さらに、同梱のTraefikがIngressコントローラーとして機能することも確認した。Ingressリソースを1枚適用しただけで、TraefikのLoadBalancerService(実体はServiceLBが払い出すNodePort)経由でも同じnginxに到達できた。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-nginx
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-nginx
            port:
              number: 80
$ curl -s -i http://172.17.0.4:32498/   # traefik ServiceのNodePort
HTTP/1.1 200 OK
...
<title>Welcome to nginx!</title>

kube-apiserverにIngressをapplyしただけで、Traefikが自動でルーティング設定を反映していた(手動でTraefikを再起動・再設定する必要は一切なかった)。KubernetesのIngressコントローラーが「宣言的にapplyするだけでロードバランサ設定が反映される」という仕組みを実感できた。

後片付け

docker stop k3s-experiment && docker rm k3s-experiment

イメージ(rancher/k3s:v1.36.3-k3s1、265MB)はローカルに残したので、次回はpull無しですぐ再実験できる。

コードから分かること

  • k3sの「軽量」は主に配布形態(単一バイナリ)と運用の自動化(証明書配布・Ingress自動反映など)によるもので、実行時に全部が1プロセスに収まっているわけではない。コントロールプレーンのコアAPI群だけが1プロセス化されており、アドオン(Traefik・CoreDNS等)は素のKubernetesと同じくPodとして動く。
  • Dockerコンテナ1個でCNCF準拠のフル機能Kubernetesクラスタが50秒足らずで立ち上がり、Ingress込みで実アプリの疎通まで確認できる手軽さは、minikubekindと同じ土俵の「ローカルで試すk3s」体験として十分実用的。

出典

作成日時: 2026-08-19 09:59 / 更新日時: 2026-08-19 09:59