🕌

CNI plugin の入力と出力をキャプチャする方法

2021/05/07に公開

概要

k8sでCNI pluginを使用する際、CNI pluginの入出力を確認したい場合があります。方法がいくつかある中で、plugin chainingを使用した場合を説明します。

plugin chaining

CNIにはplugin chainingという仕組みがあり、複数のpluginを順に実行することができます。例えばcni_a, cni_b, cni_cを並べることでこれらを順に実行します。 CNI_COMMANDがADDの場合はcni_a, cni_b, cni_cの順に、DELの場合はこの逆順に実行します。
この機能を利用して、例えば cni_pre, Calico, cni_post の順に並べることでCalicoの入出力を確認することができます。

準備

cni_preとcni_postのpythonでの実装例です。

cni_pre.py

#!/usr/bin/python3
import os
import subprocess
import json
import sys
import tempfile
import netifaces
import hashlib
import random
import fcntl

def parseArgs():
	args = {}

	args['CNI_COMMAND'] = os.environ.get('CNI_COMMAND')
	if args['CNI_COMMAND'] == None:
		with open(LOG_FILE_NAME, mode='a') as f:
			f.write('env CNI_COMMAND not found')
		exit(1)

	args['CNI_CONTAINERID'] = os.environ.get('CNI_CONTAINERID')
	if args['CNI_CONTAINERID'] == None:
		with open(LOG_FILE_NAME, mode='a') as f:
			f.write('env CNI_CONTAINERID not found')
		exit(1)

	args['STDIN_DATA'] = json.load(sys.stdin)

	return args

def cmdAdd(args):
	with open(LOG_FILE_NAME, mode='a') as f:
		f.write('CNI_COMMAND=')
		f.write(args['CNI_COMMAND'])
		f.write(',CNI_CONTAINERID=')
		f.write(args['CNI_CONTAINERID'])
		f.write(',input=')
		f.write(json.dumps(args['STDIN_DATA']))
		f.write('\n')
	print(json.dumps({'cniVersion': '0.4.0'}))

def cmdDel(args):
	print("{}")

def cmdCheck(args):
	print("{}")

LOG_FILE_NAME = '/tmp/cni.log'
LOCK_FILE_NAME = '/var/run/lock/cni.lock'

args = parseArgs()

if args['CNI_COMMAND'] == 'VERSION':
	print('{"cniVersion": "0.4.0", "supportedVersions": ["0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"]}')
	exit(0)

lockfp = open(LOCK_FILE_NAME, "w")
fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX)

if args['CNI_COMMAND'] == 'ADD':
	cmdAdd(args)
elif args['CNI_COMMAND'] == 'DEL':
	cmdDel(args)
elif args['CNI_COMMAND'] == 'CHECK':
	cmdCheck(args)

fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN)
lockfp.close()

cni_post.py

#!/usr/bin/python3
import os
import subprocess
import json
import sys
import tempfile
import netifaces
import hashlib
import random
import fcntl

def parseArgs():
	args = {}

	args['CNI_COMMAND'] = os.environ.get('CNI_COMMAND')
	if args['CNI_COMMAND'] == None:
		with open(LOG_FILE_NAME, mode='a') as f:
			f.write('env CNI_COMMAND not found')
		exit(1)

	args['CNI_CONTAINERID'] = os.environ.get('CNI_CONTAINERID')
	if args['CNI_CONTAINERID'] == None:
		with open(LOG_FILE_NAME, mode='a') as f:
			f.write('env CNI_CONTAINERID not found')
		exit(1)

	args['STDIN_DATA'] = json.load(sys.stdin)

	return args

def cmdAdd(args):
	with open(LOG_FILE_NAME, mode='a') as f:
		f.write('CNI_COMMAND=')
		f.write(args['CNI_COMMAND'])
		f.write(',CNI_CONTAINERID=')
		f.write(args['CNI_CONTAINERID'])
		f.write(',output=')
		f.write(json.dumps(args['STDIN_DATA']['prevResult']))
		f.write('\n')
	print(json.dumps(args['STDIN_DATA']['prevResult']))

def cmdDel(args):
	with open(LOG_FILE_NAME, mode='a') as f:
		f.write('CNI_COMMAND=')
		f.write(args['CNI_COMMAND'])
		f.write(',CNI_CONTAINERID=')
		f.write(args['CNI_CONTAINERID'])
		f.write(',input=')
		f.write(json.dumps(args['STDIN_DATA']))
		f.write('\n')
	print("{}")

def cmdCheck(args):
	print("{}")

LOG_FILE_NAME = '/tmp/cni.log'
LOCK_FILE_NAME = '/var/run/lock/cni.lock'

args = parseArgs()

if args['CNI_COMMAND'] == 'VERSION':
	print('{"cniVersion": "0.4.0", "supportedVersions": ["0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"]}')
	exit(0)

lockfp = open(LOCK_FILE_NAME, "w")
fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX)

if args['CNI_COMMAND'] == 'ADD':
	cmdAdd(args)
elif args['CNI_COMMAND'] == 'DEL':
	cmdDel(args)
elif args['CNI_COMMAND'] == 'CHECK':
	cmdCheck(args)

fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN)
lockfp.close()

これらをCalicoの前後で実行するために、Calicoの設定ファイルを編集します。
/etc/cni/net.d/10-calico.conflistplugins配列の先頭にcni_pre.pyを、末尾にcni_post.pyを追加します。

--- 10-calico.conflist.prev	2021-05-07 17:57:21.252206104 +0900
+++ 10-calico.conflist	2021-05-07 17:56:43.820039515 +0900
@@ -3,6 +3,9 @@
   "cniVersion": "0.3.1",
   "plugins": [
     {
+      "type": "cni_pre.py"
+    },
+    {
       "type": "calico",
       "log_level": "info",
       "log_file_path": "/var/log/calico/cni/cni.log",
@@ -27,6 +30,9 @@
     {
       "type": "bandwidth",
       "capabilities": {"bandwidth": true}
+    },
+    {
+      "type": "cni_post.py"
     }
   ]
 }

実行

下記コマンドでpodを起動します。

$ kubectl run nginx --image=nginx

該当Nodeにて/tmp/cni.logを開いて内容を確認します。
ADDコマンドの場合に{"cniVersion": "0.3.1", "name": "k8s-pod-network", "type": "cni_pre.py"}を受け取って{"cniVersion": "0.3.1", "interfaces": [{"name": "calic440f455693"}], "ips": [{"version": "4", "address": "10.244.19.140/32"}], "dns": {}}を返していることがわかります。

CNI_COMMAND=ADD,CNI_CONTAINERID=280718f61fd143ce1e75e9a40557129559098a9fb911d909a07f84fd6f8532e2,input={"cniVersion": "0.3.1", "name": "k8s-pod-network", "type": "cni_pre.py"}
CNI_COMMAND=ADD,CNI_CONTAINERID=280718f61fd143ce1e75e9a40557129559098a9fb911d909a07f84fd6f8532e2,output={"cniVersion": "0.3.1", "interfaces": [{"name": "calic440f455693"}], "ips": [{"version": "4", "address": "10.244.19.140/32"}], "dns": {}}

Discussion