LoginSignup
3
2

さようならサイドカー、Linux カーネル テクノロジ eBPF 基盤の Cilium を Docker で試した

Last updated at Posted at 2023-06-02

はじめに

Cilium を用いた TLS セッション終端を Dockerコンテナで試したので手順を紹介します。

Cilium ドキュメントに次のようにありますが

Cilium は、Docker や Kubernetes などの Linux コンテナ管理プラットフォームを使用してデプロイされたアプリケーション サービス間のネットワーク接続を透過的に保護するためのオープン ソース ソフトウェアです。

Cilium ドキュメント(v1.13)のイントロダクションより抜粋

ドキュメントを読み進めると、Kubernetes の手順はありますが、Docker の手順は見当たりません。古いドキュメント(Cilium v1.9)に Docker の手順がありますが、Key-Valueストアの consul が非推奨となったため etcd に変更すると、その手順では動作しませんでした。

モチベーション

Cilium の基盤となっているのは、eBPF と呼ばれる新しい Linux カーネル テクノロジです。これにより、強力なセキュリティの可視性と制御ロジックを動的に挿入できます。eBPF は Linux カーネル内で実行されるため、アプリケーションコードやコンテナー構成を変更することなくセキュリティ ポリシーを適用および更新できます。

次のブログ記事によると

次のようなネットワーク層の機能はビジネスロジックから切り離したいです。

  • パケットやリクエストのフィルタリング
  • セキュア通信
  • シンプルなネットワーキング(Calico 等の代替)
  • 負荷分散、経路制御
  • 帯域幅管理
  • モニタリングとトラブル要因解析

そのためにこれまでは、共有ライブラリなどでコードを分離したり、プロキシやサイドカーでプロセスを分離するといったアプローチがとられてきましたが、eBPF テクノロジを用いることでこれらの機能をカーネル空間へ移動することで(次図)

295_2.png
(上図は元記事からの引用)

これまでプロキシによって実行されていた機能の多くをオフロードすることができ、オーバーヘッドと複雑さを軽減できる可能性があるとのこと。

本稿では、異なるノードに配置された Dockerコンテナ app を Cilium を用いてオーバーレイネットワークで接続し、一方に TLS終端を設定し動作を確認します。

   Node1 のコンテナ app1 と Node2 のコンテナ app2 を接続
qiita-cilium-env.png

検証環境

Node1 と Node2 は同一構成としました。

$ uname -a
Linux debian11 5.10.0-21-amd64 #1 SMP Debian 5.10.162-1 (2023-01-21) x86_64 GNU/Linux

$ docker --version
Docker version 23.0.5, build bc4487a

$ docker compose exec -it cilium cilium version
Client: 1.13.2 8cb94c70 2023-04-17T23:19:21+02:00 go version go1.19.8 linux/amd64
Daemon: 1.13.2 8cb94c70 2023-04-17T23:19:21+02:00 go version go1.19.8 linux/amd64

Cilium のシステム動作要件は、次を参照ください。

Cilium コンポーネント概観

Docker と Cilium は、プラグインcilium-dockerDocker ネットワークドライバープラグイン)を介してやり取りします。

docker CLI で Dockerネットワークを作成すると dockerd は、cilium-dockerプラグインを介して cilium-agent にネットワークの作成を要求します。ネットワーク作成後、そのネットワークでコンテナを開始すると dockerd は、cilium-dockerプラグインを介してcilium-agent に IPアドレスの割り当てを要求します。コンテナ間の通信に何らかのポリシーを適用したいときは、cilium CLI でcilium-agent へポリシーを適用します。

cilium-cilium-docker.png

  • Cilium エージェントcilium-agent)は、クラスター内の各ノードで実行されるデーモンです。ネットワーキングやサービス負荷分散、ネットワーク ポリシー、および可視性と監視の構成を API を介して受け入れ、ネットワークアクセスを制御するための eBPF プログラムを管理します。
  • Cilium CLIcilium)は、Cilium エージェントと共にインストールされるコマンドラインツールです。同じノードで実行されている Cilium エージェントの API と対話しネットワークポリシーを構成したりエージェントの状態を調べることができます。
  • Cilium オペレータは、クラスター全体に対して論理的に 1 回処理する必要があるタスクを管理します。
  • プラグインcilium-cnicilium-docker) は、Kubernetes または Docker によって呼び出されます。ノードの Cilium エージェントの API と対話して必要なデータパス構成をトリガーし、ネットワーク、負荷分散、およびネットワーク ポリシーを構成します。
  • データストアは、Ciliumエージェント間で状態を伝達するために使用します。

ここでの Cilium CLI は、別途 Kubernetes クラスター上に Cilium をインストール/管理するための CLI ツール cilium と同じ呼称ですが別物なので混同しないようにしてください。

データストア

Cilium では、エージェント間で状態を伝達するためのデータストアが必要です。次のデータストアがサポートされています。

  • Kubernetes CRD
  • Key-Valueストア(KVストア)

ここでは KVストアを用います。これまで Cilium は、KVストアとして consuletcd をサポートしてきましたが、リリース 1.11 で consul は非​​推奨となりました。

consul を用いると Ciliumエージェントは、実行時ログに次の deprecated 警告を出力しますが、まだ動作するようです。

Support for Consul as a kvstore backend has been deprecated due to lack of maintainers. If you are interested in helping to maintain Consul support in Cilium, please reach out on GitHub or the official Cilium slack

他方 etcd を用いると Ciliumエージェントは、開始数分後にステータスが "KVStore: Failure Err: quorum check failed" となりエラー終了します。これは、Ciliumエージェントが定期的に etcd のハートビート・キー(cilium/.heartbeat)の更新を監視しハートビートキーが時間内に更新されない場合、クォーラムチェックに失敗したとみなされるためです。

調べた限りでは、現時点で設定だけでエラー終了を回避することはできず、Cilium オペレータを常駐させハートビート・キーを定期的に更新する必要がありました。

Cilium 実行

Docker-Compose を用いて Node1 と Node2 のそれぞれで Ciliumコンテナを開始します。

$ export NODE1_ADDR=<your-node1-ip-address>
$ export NODE2_ADDR=<your-node2-ip-address>
$ docker compose up -d

コンテナ配置図
qiita-cilium-container.png

Node2 の cilium-agent は、Node1 の KVストア etcd を参照します。cilium CLI は、Cilium エージェントに同梱されています。

Node1 の docker-compose.yml の例
Node1: docker-compose.yml
version: '3'
services:
  cilium:
    container_name: cilium
    image: quay.io/cilium/cilium:v1.13.3
    command: >
      cilium-agent
      --ipv4-node="${NODE1_ADDR}"
      --enable-ipv6=false
      --kvstore=etcd
      --kvstore-opt=etcd.address=127.0.0.1:2379
      --tunnel=vxlan
      --cluster-name=local
      --cluster-id=0
    volumes:
      - /var/run/cilium:/var/run/cilium
      - /sys/fs/bpf:/sys/fs/bpf
      - /var/run/docker/netns:/var/run/docker/netns:rshared
      - /var/run/netns:/var/run/netns:rshared
      - /boot:/boot/
      - /lib/modules:/lib/modules/
      - /usr/lib/modules:/usr/lib/modules/
    network_mode: "host"
    cap_add:
      - "NET_ADMIN"
    privileged: true
    depends_on:
      - kvstore

  plugin:
    container_name: plugin
    image: quay.io/cilium/docker-plugin:v1.13.3
    command: cilium-docker
    volumes:
      - /var/run/cilium:/var/run/cilium
      - /run/docker/plugins:/run/docker/plugins
      - /var/run/docker.sock:/var/run/docker.sock
    network_mode: "host"
    cap_add:
      - "NET_ADMIN"
    privileged: true
    depends_on:
      - cilium

  kvstore:
    container_name: kvstore
    image: quay.io/coreos/etcd:v3.5.8
    command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379
    network_mode: "host"

  operator:
    container_name: operator
    image: quay.io/cilium/operator-generic:v1.13.3
    command: >
      cilium-operator-generic
      --enable-ipv6=false
      --kvstore=etcd
      --kvstore-opt=etcd.address=127.0.0.1:2379
      --enable-k8s=false
      --cluster-pool-ipv4-cidr=10.0.0.0/16
    network_mode: "host"
    depends_on:
      - kvstore
Node2 の docker-compose.yml の例
Node2: docker-compose.yml
version: '3'
services:
  cilium:
    container_name: cilium
    image: quay.io/cilium/cilium:v1.13.3
    command: >
      cilium-agent
      --ipv4-node="${NODE2_ADDR}"
      --enable-ipv6=false
      --kvstore=etcd
      --kvstore-opt=etcd.address="${NODE1_ADDR}":2379
      --tunnel=vxlan
      --cluster-name=local
      --cluster-id=0
      --certificates-directory=/opt/cilium/certs
    volumes:
      - /var/run/cilium:/var/run/cilium
      - /sys/fs/bpf:/sys/fs/bpf
      - /var/run/docker/netns:/var/run/docker/netns:rshared
      - /var/run/netns:/var/run/netns:rshared
      - ./tls:/opt/cilium/certs/my-tls/
      - ./policy:/opt/cilium/policy/
    network_mode: "host"
    cap_add:
      - "NET_ADMIN"
    privileged: true

  plugin:
    container_name: plugin
    image: quay.io/cilium/docker-plugin:v1.13.3
    command: cilium-docker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/run/cilium:/var/run/cilium
      - /run/docker/plugins:/run/docker/plugins
    network_mode: "host"
    cap_add:
      - "NET_ADMIN"
    privileged: true
    depends_on:
      - cilium

Node1 と Node2 を同一クラスターのメンバーとするために cilium-agent の起動引数 --cluster-name--cluster-id に同じ値を指定します。

コンテナ cilium に接続し cilium CLI でステータスを確認

$ docker exec -it cilium cilium status
KVStore:                 Ok         etcd: 1/1 connected, lease-ID=694d886b8f79e705, lock lease-ID=694d886b8f79e707, has-quorum=true: 127.0.0.1:2379 - 3.5.8 (Leader)
Kubernetes:              Disabled
Host firewall:           Disabled
CNI Chaining:            none
CNI Config file:         CNI configuration file management disabled
Cilium:                  Ok   1.13.3 (v1.13.3-36cb0eed)
NodeMonitor:             Listening for events on 1 CPUs with 64x4096 of shared memory
Cilium health daemon:    Ok
IPAM:                    IPv4: 2/65534 allocated from 10.13.0.0/16,
IPv6 BIG TCP:            Disabled
BandwidthManager:        Disabled
Host Routing:            Legacy
Masquerading:            IPTables [IPv4: Enabled, IPv6: Disabled]
Controller Status:       21/21 healthy
Proxy Status:            OK, ip 10.13.50.180, 0 redirects active on ports 10000-20000
Global Identity Range:   min 256, max 65535
Hubble:                  Disabled
Encryption:              Disabled
Cluster health:          2/2 reachable   (2023-05-30T07:31:08Z)

Node1 と Node2 が正常に認識されるまで数分ほど待ちます。正常に認識されると Cluster health2/2 reachable になります。

クラスターノードに割り当てられた CIDR を確認

$ docker exec -it cilium cilium node ls
Name          IPv4 Address      Endpoint CIDR   IPv6 Address   Endpoint CIDR
local/node1   192.168.250.154   10.13.0.0/16
local/node2   192.168.251.138   10.234.0.0/16

アプリケーション コンテナ app 実行

Node1 と Node2 でそれぞれコンテナ app1app2 を開始しします。

Node1

Dockerネットワーク cilium-net 作成。ドライバーに cilium を指定していることに注意

Node1
$ docker network create --driver cilium --ipam-driver cilium cilium-net

コンテナ app1 を開始しシェル接続

Node1
$ docker run -it --rm --name app1 -l name=app1 --net cilium-net alpine/curl sh

ここでオプション--netcilium-net を指定していることに注意してください。プラグイン cilium-docker を介して IPアドレスが払い出されます。

コンテナ app1 の IPv4アドレスを確認

app1
/ # ip a
...
45: cilium0@if46: ...
    inet 10.13.165.153/32 scope global cilium0
...

Cilium エンドポイントにコンテナapp1 が追加されたことを確認

Node1
$ docker exec -it cilium cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])   IPv6   IPv4            STATUS
           ENFORCEMENT        ENFORCEMENT
31         Disabled           Disabled          2532       container:name=app1                  10.13.165.153   ready
...

Node2

Dockerネットワーク cilium-net を作成

Node2
$ docker network create --driver cilium --ipam-driver cilium cilium-net

コンテナ app2 を開始しシェル接続

Node2
$ docker run -it --rm --name app2 -l name=app2 --net cilium-net busybox sh

コンテナ app2 の IPv4アドレスを確認

app2
/ # ip a
...
35: cilium0@if36: ...
    inet 10.234.96.18/32 scope global cilium0
...

Cilium エンドポイントにコンテナapp2 が追加されたことを確認

Node2
$  docker exec -it cilium cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])   IPv6   IPv4             STATUS
           ENFORCEMENT        ENFORCEMENT
3948       Disabled           Disabled          15836      container:name=app2                  10.234.96.18     ready
...

疎通確認

Node1 のコンテナ app1 から Node2 のコンテナ app2 への疎通を確認します。

IPアドレス
qiita-cilium-ip.png

コンテナ app2 でポート 9999 をリッスン

app2
/ # nc -l -p 9999

コンテナ app1 から app2 へメッセージ送信

app1
/ # echo hoge | nc 10.234.96.18:9999

コンテナ app2 でメッセージを受信

app2
/ # nc -l -p 9999
hoge

TLS終端

Cilium ネットワークポリシーを適用し Node2 のコンテナ app2 に TLS 終端を設定します。

Node2

サーバ秘密鍵とサーバ証明書、Cilium ネットワークポリシーファイルを準備します。サーバ証明書とサーバ秘密鍵の作成手順は割愛します。

フォルダ構成

.
├── docker-compose.yml
├── profile
│   └── profile.json   # Cilium ネットワークポリシーファイル
└── tls
    ├── tls.crt        # サーバ証明書
    └── tls.key        # サーバ秘密鍵

Cilium ネットワークポリシーファイルを作成

Node2: profile/profile.json
[{
    "labels": [{"key": "name", "value": "tls-termination"}],
    "endpointSelector": {"matchLabels":{"name":"app2"}},
    "ingress": [{
        "fromEndpoints": [
            {"matchLabels":{"name":"app1"}}
        ],
        "toPorts": [{
            "ports": [{"port": "443", "protocol": "TCP"}],
            "terminatingTLS": {
                "secret": {
                    "name": "my-tls"
                },
                "certificate": "tls.crt",
                "privateKey": "tls.key"
            },
            "rules": {
                "http": [{}]
            }
        }]
    }]
}]

ラベル name=app2 のエンドポイントに適用するポリシーです。ラベル name=app1 からの ingress ポート443 をTLS終端します。

terminatingTLS.secret.name は、サーバ証明書を配置するフォルダパス(コンテナ内)の一部となります。この場合、cilium-agent の起動引数に --certificates-directory=/opt/cilium/certs を指定しているので次の場所にサーバ証明書と秘密鍵を配置する必要があります。

  • /opt/cilium/certs/my-tls/tls.crt
  • /opt/cilium/certs/my-tls/tls.key

参照:https://github.com/cilium/cilium/blob/v1.13.3/pkg/crypto/certificatemanager/certificate_manager.go#L42

CA証明書ファイル ca.crt(デフォルト)が存在する場合やオプション terminatingTLS.trustedCA を指定すると mTLS になるようです。

Cilium ネットワークポリシーを適用

Node2
$ docker exec -it cilium cilium policy import /opt/cilium/policy/policy.json

ポリシーが適用されたことを確認

Node2
$ docker exec -it cilium cilium endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])   IPv6   IPv4             STATUS
           ENFORCEMENT        ENFORCEMENT
3948       Enabled            Disabled          15836      container:name=app2                  10.234.96.18     ready
...

POLICY (ingress)Enabled であれば ok.

ポリシーの詳細を取得

$ docker exec -it cilium cilium policy get name=tls-termination

全てのポリシーを削除

$ docker exec -it cilium cilium policy delete --all

動作確認

コンテナ app2 でポート 443 をリッスン

app2
/ # nc -l -p 443

コンテナ app1 から app2 へ HTTPS リクエストを送信

app1
/ # curl --insecure https://10.234.96.18/

コンテナ app2 でリクエストを受信

app2
/ # nc -l -p 443
GET / HTTP/1.1
host: 10.234.96.18
user-agent: curl/7.80.0
accept: */*
x-forwarded-proto: https
x-request-id: 1395028f-32ed-4887-aa8c-0defa89bd236
x-envoy-expected-rq-timeout-ms: 3600000

TLS終端され平文で受信できました。

3
2
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
3
2