前回の telepresence 入門 (1) の続きです。今回は Kubernetes クラスタの Service へのアクセスをインターセプトして手元の環境に転送することを試します。Kubernetes 側の volume も手元で mount させるし、環境変数も引っ張ってきます。 バージョンなど環境については前回と同じ。
インターセプト設定
Kubernetes の Service 宛の通信を手元に転送することを intercept と呼ぶようです。
インターセプト前の状態
前回使った hello という Service, Deployment を使います。
$ kubectl create deploy hello --image=k8s.gcr.io/echoserver:1.4
$ kubectl expose deploy hello --port 80 --target-port 8080
Service は port 80 をコンテナの port 8080 に転送するようになっています。
$ kubectl get svc hello -o jsonpath={.spec.ports} | jq
[
{
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
]
telepresence intercept
コマンド実行前の状態です。(traffic-agent not yet installed)
ということで agent がまだインストールされていません。
$ telepresence list
hello: ready to intercept (traffic-agent not yet installed)
インターセプト
telepresence intercept
コマンドを実行します。--port
はローカルの転送先 port の指定です。デフォルトが 8080 です。
$ telepresence intercept hello --port 8080
Using Deployment hello
intercepted
Intercept name : hello
State : ACTIVE
Workload kind : Deployment
Destination : 127.0.0.1:8080
Volume Mount Point: /var/folders/nd/8mk6834s31g8dymd1_9pnqq00000gn/T/telfs-3519194956
Intercepting : all TCP connections
これで、手元で port 8080 で Listen するプロセスを実行すれば Kubernetes 内で hello サービスにアクセスするとそこに流れてきます。例えば python で HTTP サーバーを起動させて、Kubernetes の別 Pod から curl -s http://hello.default/
を実行してみたら次のように 127.0.0.1 からのアクセスが行われました。
$ python3 -m http.server 8080
Serving HTTP on :: port 8080 (http://[::]:8080/) ...
::ffff:127.0.0.1 - - [08/Jan/2022 11:01:18] "GET / HTTP/1.1" 200 -
手元のプロセスは docker などで実行しても問題ありません。
$ docker run -it --rm -p 8080:80 nginx:latest
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/01/08 02:05:31 [notice] 1#1: using the "epoll" event method
2022/01/08 02:05:31 [notice] 1#1: nginx/1.21.5
2022/01/08 02:05:31 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/01/08 02:05:31 [notice] 1#1: OS: Linux 5.13.0-23-generic
2022/01/08 02:05:31 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/01/08 02:05:31 [notice] 1#1: start worker processes
2022/01/08 02:05:31 [notice] 1#1: start worker process 31
2022/01/08 02:05:31 [notice] 1#1: start worker process 32
2022/01/08 02:05:31 [notice] 1#1: start worker process 33
2022/01/08 02:05:31 [notice] 1#1: start worker process 34
172.17.0.1 - - [08/Jan/2022:02:05:36 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.64.0" "-"
telepresence intercept
コマンドの --docker-run
オプションを使うと intercept の有効化と同時に docker run も実行してくれます。
図解
こんな感じっぽいです。telepresence intercept
を実行すると Service に対応する Deployment などに対して traffic agent コンテナが追加されます(このため Pod は再作成されます)。また、Service の targetPort が書き換えられます。
追加されるコンテナ manifest の例
spec:
template:
spec:
containers:
- args:
- agent
env:
- name: TELEPRESENCE_CONTAINER
value: echoserver
- name: _TEL_AGENT_LOG_LEVEL
value: info
- name: _TEL_AGENT_NAME
value: hello
- name: _TEL_AGENT_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: _TEL_AGENT_POD_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: _TEL_AGENT_APP_PORT
value: "8080"
- name: _TEL_AGENT_PORT
value: "9900"
- name: _TEL_AGENT_MANAGER_HOST
value: traffic-manager.ambassador
image: docker.io/datawire/tel2:2.4.9
imagePullPolicy: IfNotPresent
name: traffic-agent
ports:
- containerPort: 9900
name: tx-8080
protocol: TCP
readinessProbe:
exec:
command:
- /bin/stat
- /tmp/agent/ready
failureThreshold: 3
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /tel_pod_info
name: traffic-annotations
インターセプト前の Service
$ kubectl get svc hello -o jsonpath={.spec.ports} | jq
[
{
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
]
インターセプト後の Service では targetPort が tx-8080
に書き換えられており、これは追加された traffic-agent コンテナの 9900 port を指している。
$ kubectl get svc hello -o jsonpath={.spec.ports} | jq
[
{
"port": 80,
"protocol": "TCP",
"targetPort": "tx-8080"
}
]
インターセプトの中断
telepresence leave
コマンドでインターセプトを中断できます。中断では Deployment や Service の書き換えはそのままで、agent の振る舞いが変更されるようです。traffic-agent は元のコンテナの port に転送することになるようです。
$ telepresence list
hello: ready to intercept (traffic-agent already installed)
traffic-agent の削除
telepresence uninstall
で書き換えた変更を戻します。また Deployment などが書き換えられるので Pod は再作成されます。
$ telepresence uninstall --agent hello
(traffic-agent not yet installed)
に戻りました。
$ telepresence list
hello: ready to intercept (traffic-agent not yet installed)
全ての agent を削除する場合は telepresence uninstall --all-agents
が使えし、traffic-managet ごと削除する --everything
というオプションもあります。
sshfs のインストール
telepresence では転送元 Pod がマウントしている volume に手元からもアクセスできるようにすることができますが、(mac では) これに sshfs が使われるため、手元の環境にインストールしておく必要があります。が、 brew install sshfs
すると closed-source な macFUSE が必要だから無効になってるよとインストールできません。
Error: sshfs has been disabled because it requires closed-source macFUSE!
よって、まず、github.com/osxfuse/osxfuse/releases にある macFUSE をインストールし、 その後に github.com/gromgit/homebrew-fuse にある Homebrew Formula を使ってインストールします。 このリポジトリは mscFUSE が closed-source になってしまったことで Homebrew の core から削られてしまったものをインストールするために作られたもののようです。
$ brew install gromgit/fuse/sshfs-mac
環境変数や volume のマウント
telepresence を使うと intercept 対象の Pod に設定されている環境変数を docker 用や json として取得し、使うことができますし、Pod がマウントしている volume を手元でマウントすることが可能で、それをさらに docker コンテナにマウントすることもできます。
普通に telepresence intercept コマンドを実行するだけでも転送先に docker コンテナを使うことは可能ですが、--docker-run
というオプションがあり、これを使うことで telepresence intercept
コマンド実行で一緒に docker run までしてくれるようになります。
テスト用に Wordpress をデプロイする
wordpress と mysql との通信もあるし、volume マウントもあって動作確認に便利そうということで bitnami の helm chart を使って wordpress をデプロイします。この chart は wordpress が NFS などの ReadWriteMany な volume を期待しているのですが、用意するのが面倒なので ReadWriteOne でも使えるように指定しています。(helm3 では --set
で null を指定することで default の key を消すということができないので -f
で指定。外部に公開する必要もないので service の type は LoadBalancer から ClusterIP に変更。updateStrategy は Recreate にしないと volume が手放せないので intercept 時の Pod の再作成で詰まる)
$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm repo update
$ kubectl create namespace wordpress
$ kubens wordpress
$ helm install wordpress bitnami/wordpress -f - <<EOF
updateStrategy:
type: Recreate
rollingUpdate: null
service:
type: ClusterIP
EOF
こんな状態になります。
$ kubectl get svc,pod,pv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/wordpress ClusterIP 172.16.1.117 80/TCP,443/TCP 5m52s
service/wordpress-mariadb ClusterIP 172.16.7.205 3306/TCP 5m52s
NAME READY STATUS RESTARTS AGE
pod/wordpress-7c688c7f48-r8mrf 1/1 Running 0 5m45s
pod/wordpress-mariadb-0 1/1 Running 0 5m45s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-07259eb1-aa1c-4521-9029-e57f12b2eedc 8Gi RWO Delete Bound wordpress/data-wordpress-mariadb-0 standard 5m49s
persistentvolume/pvc-4f85f20c-e376-4489-91df-b126cf029967 10Gi RWO Delete Bound wordpress/wordpress standard 5m50s
wordpress で使われているコンテナイメージを確認
$ kubectl get deploy wordpress -o 'jsonpath={.spec.template.spec.containers[0].image}'
docker.io/bitnami/wordpress:5.8.2-debian-10-r47
volume のマウント位置を確認
$ **kubectl get deploy wordpress -o 'jsonpath={.spec.template.spec.containers[0].volumeMounts}' | jq**
[
{
"mountPath": "/bitnami/wordpress",
"name": "wordpress-data",
"subPath": "wordpress"
}
]
環境変数のファイルへの書き出しと volume のマウント
ここで、次のようにすることで環境変数を wordpress.env というファイルに、Pod がマウントしている volume を wordpress.vol ディレクトリ配下にマウントすることができます。 (wordpress Service は http の 8080 と https の 8443 の2つの port があるので --port 8080:http
と明示しています)
$ telepresence intercept wordpress --port 8080:http --env-file ./wordpress.env --mount ./wordpress.vol
Using Deployment wordpress
intercepted
Intercept name : wordpress
State : ACTIVE
Workload kind : Deployment
Destination : 127.0.0.1:8080
Service Port Identifier: http
Volume Mount Point : ./wordpress.vol
Intercepting : all TCP connections
wordpress.env の中身
$ cat wordpress.env
ALLOW_EMPTY_PASSWORD=yes
APACHE_HTTPS_PORT_NUMBER=8443
APACHE_HTTP_PORT_NUMBER=8080
BITNAMI_DEBUG=false
KO_DATA_PATH=/var/run/ko
KUBERNETES_PORT=tcp://172.16.0.1:443
KUBERNETES_PORT_443_TCP=tcp://172.16.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=172.16.0.1
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=172.16.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
MARIADB_HOST=wordpress-mariadb
MARIADB_PORT_NUMBER=3306
TELEPRESENCE_CONTAINER=wordpress
TELEPRESENCE_INTERCEPT_ID=bd126583-7aad-4e8b-b00c-09890e027630:wordpress
TELEPRESENCE_MOUNTS=/bitnami/wordpress:/var/run/secrets/kubernetes.io
TELEPRESENCE_ROOT=./wordpress.vol
WORDPRESS_AUTO_UPDATE_LEVEL=none
WORDPRESS_BLOG_NAME=User's Blog!
WORDPRESS_DATABASE_NAME=bitnami_wordpress
WORDPRESS_DATABASE_PASSWORD=kwSIdkeXEC
WORDPRESS_DATABASE_USER=bn_wordpress
WORDPRESS_EMAIL=user@example.com
WORDPRESS_ENABLE_HTACCESS_PERSISTENCE=no
WORDPRESS_EXTRA_WP_CONFIG_CONTENT=
WORDPRESS_FIRST_NAME=FirstName
WORDPRESS_HTACCESS_OVERRIDE_NONE=no
WORDPRESS_LAST_NAME=LastName
WORDPRESS_MARIADB_PORT=tcp://172.16.7.205:3306
WORDPRESS_MARIADB_PORT_3306_TCP=tcp://172.16.7.205:3306
WORDPRESS_MARIADB_PORT_3306_TCP_ADDR=172.16.7.205
WORDPRESS_MARIADB_PORT_3306_TCP_PORT=3306
WORDPRESS_MARIADB_PORT_3306_TCP_PROTO=tcp
WORDPRESS_MARIADB_SERVICE_HOST=172.16.7.205
WORDPRESS_MARIADB_SERVICE_PORT=3306
WORDPRESS_MARIADB_SERVICE_PORT_MYSQL=3306
WORDPRESS_PASSWORD=CZS97FQnB3
WORDPRESS_PLUGINS=none
WORDPRESS_PORT=tcp://172.16.1.117:80
WORDPRESS_PORT_443_TCP=tcp://172.16.1.117:443
WORDPRESS_PORT_443_TCP_ADDR=172.16.1.117
WORDPRESS_PORT_443_TCP_PORT=443
WORDPRESS_PORT_443_TCP_PROTO=tcp
WORDPRESS_PORT_80_TCP=tcp://172.16.1.117:80
WORDPRESS_PORT_80_TCP_ADDR=172.16.1.117
WORDPRESS_PORT_80_TCP_PORT=80
WORDPRESS_PORT_80_TCP_PROTO=tcp
WORDPRESS_SCHEME=http
WORDPRESS_SERVICE_HOST=172.16.1.117
WORDPRESS_SERVICE_PORT=80
WORDPRESS_SERVICE_PORT_HTTP=80
WORDPRESS_SERVICE_PORT_HTTPS=443
WORDPRESS_SKIP_BOOTSTRAP=no
WORDPRESS_TABLE_PREFIX=wp_
WORDPRESS_USERNAME=user
volume は指定したディレクトリ配下に Pod 内でマウントされていた path でマウントされます。/bitnami/wordpress
と /var/run/secrets/kubernetes.io/serviceaccount
$ find wordpress.vol -maxdepth 3 -type d
wordpress.vol
wordpress.vol/bitnami
wordpress.vol/bitnami/wordpress
wordpress.vol/bitnami/wordpress/wp-content
wordpress.vol/var
wordpress.vol/var/run
wordpress.vol/var/run/secrets
元のコンテナはこうなってて
volumeMounts:
- mountPath: /bitnami/wordpress
name: wordpress-data
subPath: wordpress
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-cdcc8
readOnly: true
traffic-agent コンテナではこうなっています
volumeMounts:
- mountPath: /tel_app_mounts/bitnami/wordpress
name: wordpress-data
subPath: wordpress
- mountPath: /tel_pod_info
name: traffic-annotations
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-cdcc8
readOnly: true
traffic-agent が sftp プロトコルをサポートしてて、sshfs でマウントできるようになっています。
$ mount | grep wordpress
localhost:/tel_app_mounts on /Users/teraoka/wordpress.vol (macfuse, nodev, nosuid, synchronous, mounted by teraoka)
取得した環境変数と volume を使ってコンテナを起動
環境変数と volume が準備できたのでこれを使って手元で docker コンテナを実行します。
$ docker run --rm -it -p 8080:8080 \
-v $(pwd)/wordpress.vol/bitnami/wordpress:/bitnami/wordpress \
--env-file ./wordpress.env \
docker.io/bitnami/wordpress:5.8.2-debian-10-r47
$ docker run --rm -it -p 8080:8080 \
-v $(pwd)/wordpress.vol/bitnami/wordpress:/bitnami/wordpress \
--env-file ./wordpress.env \
docker.io/bitnami/wordpress:5.8.2-debian-10-r47
wordpress 05:00:16.43
wordpress 05:00:16.44 Welcome to the Bitnami wordpress container
wordpress 05:00:16.44 Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-wordpress
wordpress 05:00:16.44 Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-wordpress/issues
wordpress 05:00:16.44
wordpress 05:00:16.44 INFO ==> ** Starting WordPress setup **
realpath: /bitnami/apache/conf: No such file or directory
wordpress 05:00:16.48 INFO ==> Configuring the HTTP port
wordpress 05:00:16.49 INFO ==> Configuring the HTTPS port
wordpress 05:00:16.51 INFO ==> Configuring PHP options
wordpress 05:00:16.52 INFO ==> Validating settings in MYSQL_CLIENT_* env vars
wordpress 05:00:16.57 WARN ==> Hostname wordpress-mariadb could not be resolved, this could lead to connection issues
wordpress 05:00:16.58 WARN ==> You set the environment variable ALLOW_EMPTY_PASSWORD=yes. For safety reasons, do not use this flag in a production environment.
wordpress 05:00:16.77 INFO ==> Restoring persisted WordPress installation
wordpress 05:00:17.63 INFO ==> Trying to connect to the database server
wordpress 05:03:00.25 ERROR ==> Could not connect to the database
残念ながら Could not connect to the database
というエラーで起動しませんでした… 原因は DB サーバーのホスト名が wordpress-mariadb と指定されていることで、これは同じ namespace にいる Pod からであればアクセス可能なのですが、telepresence 経由では少なくとも namespace が付いていないと名前解決ができません。 ということなので、当該箇所を書き換えてみます。(mac の sed の -i は GNU sed の -i とは違うので gsed と指定)
$ grep DB_HOST wordpress.vol/bitnami/wordpress/wp-config.php
define( 'DB_HOST', 'wordpress-mariadb:3306' );
$ gsed -i 's/wordpress-mariadb/wordpress-mariadb.wordpress/' ./wordpress.vol/bitnami/wordpress/wp-config.php
$ grep DB_HOST wordpress.vol/bitnami/wordpress/wp-config.php
define( 'DB_HOST', 'wordpress-mariadb.wordpress:3306' );
これで再度コンテナを起動すれば無事起動しました。(環境変数の MARIADB_HOST も namespace 無しで設定されているので、volume をマウントしないで試す場合はこちらを書き換える必要があります)
現在の状態は手元のコンテナで wordpress を実行しているが、その wp-content 配下は Kubernetes 内の volume に置かれているし、DB サーバーも Kubernetes 上で実行されている mariadb が使われていることになる。
手元でブラウザから localhost:8080 で Wordpress にアクセスすることも出来るし、Kubernetes 内から Service 経由でアクセスすることもできる。
使いたい場面に遭遇したら使えそうです。