Posted at

PythonからDockerとKubernetesを使い、DockerからDockerを使い、KuberneteからKubernetesを使う

More than 1 year has passed since last update.


PythonからDockerとKubernetesを使い、DockerからDockerを使い、KuberneteからKubernetesを使う。

DockerやKubernetesは通常であればBashで操作することが多いですし、ドキュメントもBash前提で書かれています。

しかしDockerもKubernetesもAPIが公開されているので、Bash以外でも操作可能です。

例えばDockerでは公式にDockerEngine用のGolangやPythonのクライアントSDKを提供していますし、KubernetesにもPython用のクライアントがあります。

Bash以外からDockerやKubernetesを使うメリットは、非インフラエンジニアでも得意な言語でDockerやKubernetesを操作できるようになる点で、Infrastructure as Codeの一端になると思います。

というわけで、今回はPythonでDockerやKubernetesを操作してみたいと思います。

またついでに、DockerコンテナからDockerを、Kubernetes PodsからKubernetesを操作します。

このあたりは道楽で試してみたらできたことを共有するだけです。


今回書いたコード

https://github.com/shibuiwilliam/pythontodockerkube


やること

こういう構成になります。

ホストOS:CentOS7

Dockerバージョン:ce 17.03

Pythonクライアント用Dockerコンテナ:CentOS7

Pythonバージョン:3.6

KubernetesCluster:MasterNode1台構成

Kubernetesバージョン:1.9

2018-02-04_1.png


下準備

CentOS7にDocker、Kubernetesをインストールしますが、このあたりは公式ドキュメントをご参照ください。

https://docs.docker.com/install/

https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/

DockerとKubernetesがインストールできたら、Pythonクライアント用Dockerコンテナを用意します。

Python用のDockerクライアントとKubernetesクライアントはいずれもpip installできますので、コンテナイメージ作成時にこれをいれておきます。

pip install docker kubernetes

個人的に作ったDockerイメージのDockerfileは以下ですが、ひとまずPython3.6が動けば良いです。


FROM centos:latest
MAINTAINER CVUSK

ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]

# yum install packages
RUN rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \
yum -y install epel-release \
python-devel python-pip python-dev python-virtualenv zip \
wget bzip2 java-1.8.0-openjdk java-1.8.0-openjdk-devel tar unzip libXdmcp \
vi vim sudo yum-utils policycoreutils selinux-policy-targeted openssh && \
yum -y update && \
yum clean all
ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0

RUN wget https://repo.continuum.io/archive/Anaconda3-4.2.0-Linux-x86_64.sh && \
bash Anaconda3-4.2.0-Linux-x86_64.sh -b -p /opt/anaconda3 && \
rm -f Anaconda3-4.2.0-Linux-x86_64.sh
ENV PATH="/opt/anaconda3/bin:$PATH"

# pip install python libraries
RUN ipython kernel install --user && \
pip install --upgrade pip && \
conda update setuptools && \
pip install docker kubernetes

RUN jupyter notebook --generate-config && \
ipython profile create
RUN echo "c.NotebookApp.ip = '*'" >>/root/.jupyter/jupyter_notebook_config.py && \
echo "c.NotebookApp.open_browser = False" >>/root/.jupyter/jupyter_notebook_config.py && \
echo "c.InteractiveShellApp.matplotlib = 'inline'" >>/root/.ipython/profile_default/ipython_config.py

EXPOSE 8888

WORKDIR /opt/

CMD ["jupyter", "notebook"]


DockerとKubernetesを操作可能なDockerコンテナを起動する

上記で作ったPythonクライアント用Dockerイメージを起動します。

ホストのDockerEngineにアクセスするには、ホストOSの/var/run/docker.sockにアクセスする必要があります。

また、ホストのKubernetesにアクセスするにはKubernetesMasterの/root/.kube/ディレクトリにアクセスする必要があります。

というわけで-vでホストの/var/run/docker.sockと/root/.kube/をコンテナにマウントします。

docker run -it \

-v /var/run/docker.sock:/var/run/docker.sock \
-v /root/.kube/:/root/.kube/ \
-v /opt/workdir:/opt/workdir \
-w="/opt/workdir/" \
-p 8888:8888 \
--name pdk \
pythoncli:1.0 \
/bin/bash

ついでに作業環境として/opt/workdir/をマウントしておきます。

/bin/bashで起動しているのはマウントを確認するためで、必須ではありません。


DockerのPythonからDockerを使う

Docker上のPythonからホストのDockerEngineにアクセスしてみます。

まずはPythonでdockerをimportし、Dockerのクライアントを設定します。そして試しにdocker run hello-worldしてみます。

Pythonのコードは以下のようになります。

import docker

client = docker.from_env()
client.containers.run("hello-world")

これでおなじみのhello-worldがdocker pullされ、docker runします。

次は試しにDockerfileからdocker buildして起動してみます。

Dockerfileの中身は以下にします。

FROM ubuntu:16.04

MAINTAINER cvusk

RUN echo "test python client for docker" >> /tmp/test.log

CMD ["cat", "/tmp/test.log"]

これをビルドして起動します。

client.images.build(path="/opt/workdir/test", tag="ubuntu:test")

client.containers.run("ubuntu:test")

Dockerfileさえ用意しておけば、簡単にイメージをビルドして起動可能です。

もちろんテキスト処理やファイル操作でDockerfileをPythonから生成することも可能ですが、DockerのPythonクライアントというよりもテキスト処理の領域なので、今回は省略します。

DockerのPythonクライアントについて、詳しいドキュメントはこちらにありますので、ご参照ください。

http://docker-py.readthedocs.io/en/stable/index.html


DockerのPythonからKubernetesを使う

続いてPythonからKubernetesを使います。

今回はKubernetesのDeploymentでnginxを起動し、Serviceで公開してみます。

Kubernetesの設定はymlで実施するのですが、Pythonからymlファイルを生成し、DeploymentとServiceを起動します。

まずは事前準備として、Kubernetesをimportし、/root/.kube/configをロードして起動済みのPodsを一覧表示します。

from kubernetes import client as kclient

from kubernetes import config as kconfig
import yaml
import os

kconfig.load_kube_config()

v1 = kclient.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)
for i in ret.items:
print("%s\t%s\t%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))

以下のように表示されていればOKです。

2018-02-04_5.PNG

ymlを生成します。

nginxのDeploymentとService用のymlになるのですが、まずはPythonのDictで定義し、これをDumpします。

nginxdep = {'apiVersion': 'extensions/v1beta1',

'kind': 'Deployment',
'metadata': {'name': 'my-nginx'},
'spec': {'replicas': 1,
'selector': {'matchLabels': {'run': 'my-nginx'}},
'template': {'metadata': {'labels': {'run': 'my-nginx'}},
'spec': {'containers': [{'image': 'nginx',
'name': 'my-nginx',
'ports': [{'containerPort': 80}]}]}}}}
nginxsvc = {'apiVersion': 'v1',
'kind': 'Service',
'metadata': {'labels': {'run': 'my-nginx'}, 'name': 'my-nginx'},
'spec': {'externalIPs': ['35.200.42.99'],
'ports': [{'nodePort': 30000, 'port': 80, 'protocol': 'TCP'}],
'selector': {'run': 'my-nginx'},
'type': 'LoadBalancer'}}

with open("kube/nginxdep.yml", "w") as f:
f.write(yaml.dump(nginxdep, default_flow_style=False))
with open("kube/nginxsvc.yml", "w") as f:
f.write(yaml.dump(nginxsvc, default_flow_style=False))

ymlをDictで定義しているので、Pythonでプログラムで生成したり変更したりすることも簡単です。

続いてこのymlをデプロイします。

自分で使いやすいように適当に使うものをまとめたClassにしていますが、すべての機能はこちらをご参照ください。

 class KubeDeployer(object):

def __init__(self):
kconfig.load_kube_config()
self.__k8s_beta = kclient.ExtensionsV1beta1Api()
self.__k8s_core = kclient.CoreV1Api()
def createDeployment(self, filename, filedir=None):
if filedir is not None:
filename = os.path.join(filedir, filename)
with open(filename) as f:
dep = yaml.load(f)
resp = self.__k8s_beta.create_namespaced_deployment(
body=dep, namespace="default")
print("DEPLOYMENT {0} created. status={1}".format(filename, resp.metadata))
return resp
def getDeploymentList(self):
resp = self.__k8s_beta.list_deployment_for_all_namespaces()
return resp
def deleteDeployment(self, name, namespace="default", **kwargs):
body = kubernetes.client.V1DeleteOptions(**kwargs)
resp = self.__k8s_beta.delete_namespaced_deployment(name, namespace, body, **kwargs)
return resp
def deleteAllDeployment(self, namespace="default", **kwargs):
respd = self.__k8s_beta.delete_collection_namespaced_deployment(namespace)
respr = self.__k8s_beta.delete_collection_namespaced_replica_set(namespace)
respp = self.__k8s_core.delete_collection_namespaced_pod(namespace)
return respd,respr,respp

def createService(self, filename, filedir=None):
if filedir is not None:
filename = os.path.join(filedir, filename)
with open(filename) as f:
svc = yaml.load(f)
resp = self.__k8s_core.create_namespaced_service(
body=svc, namespace="default")
print("SERVICE {0} created. status={1}".format(filename, resp.metadata))
return resp
def getServiceList(self):
resp = self.__k8s_core.list_service_for_all_namespaces()
return resp
def deleteService(self, name, namespace="default", **kwargs):
resp = self.__k8s_core.delete_namespaced_service(name, namespace, **kwargs)
return resp

# create deployment and service
kd = KubeDeployer()
kdeploy = kd.createDeployment("nginxdep.yml", "/opt/workdir/kube/")
ksvc = kd.createService("nginxsvc.yml", "/opt/workdir/kube/")

これでnginxが起動し、ポート30000で公開されました。

2018-02-04_2.PNG


KubernetesからKubernetesを使う

それでは最後にKubernetesからKubernetesを使います。

上記のようにDocker上のPythonからKubernetesを使えるので、KubernetesのPodsからホストOSのKubernetesにアクセスしてKubernetesを操作することも可能です。

今回はKubernetesのJobsから、nginxのDeploymentとServiceを起動します。

以下のようにJobsを定義しておきます。

cat <<- EOF > deployjob.yml

apiVersion: batch/v1
kind: Job
metadata:
name: deployer
spec:
template:
spec:
containers:
- name: deployer
image: pythoncli:1.0
command: ["python", "/opt/workdir/python_kube.py"]
workingDir: /opt/workdir/
volumeMounts:
- mountPath: /opt/workdir/
name: workdir
- mountPath: /bin/
name: kubectl
- mountPath: /root/.kube/
name: kubecfg
volumes:
- name: workdir
hostPath:
path: /opt/workdir/
- name: kubectl
hostPath:
path: /bin/
- name: kubecfg
hostPath:
path: /root/.kube/
restartPolicy: Never
backoffLimit: 4
EOF

Commandで実行するPythonスクリプトは以下のとおりですが、前項でDockerからKubernetesを起動したものとほぼ同じになります。

from kubernetes import client as kclient

from kubernetes import config as kconfig
import yaml
import os

nginxdep = {'apiVersion': 'extensions/v1beta1',
'kind': 'Deployment',
'metadata': {'name': 'my-nginx'},
'spec': {'replicas': 1,
'selector': {'matchLabels': {'run': 'my-nginx'}},
'template': {'metadata': {'labels': {'run': 'my-nginx'}},
'spec': {'containers': [{'image': 'nginx',
'name': 'my-nginx',
'ports': [{'containerPort': 80}]}]}}}}
nginxsvc = {'apiVersion': 'v1',
'kind': 'Service',
'metadata': {'labels': {'run': 'my-nginx'}, 'name': 'my-nginx'},
'spec': {'externalIPs': ['35.200.42.99'],
'ports': [{'nodePort': 30000, 'port': 80, 'protocol': 'TCP'}],
'selector': {'run': 'my-nginx'},
'type': 'LoadBalancer'}}
with open("kube/nginxdep.yml", "w") as f:
f.write(yaml.dump(nginxdep, default_flow_style=False))
with open("kube/nginxsvc.yml", "w") as f:
f.write(yaml.dump(nginxsvc, default_flow_style=False))

class KubeDeployer(object):
def __init__(self):
kconfig.load_kube_config()
self.__k8s_beta = kclient.ExtensionsV1beta1Api()
self.__k8s_core = kclient.CoreV1Api()
def createDeployment(self, filename, filedir=None):
if filedir is not None:
filename = os.path.join(filedir, filename)
with open(filename) as f:
dep = yaml.load(f)
resp = self.__k8s_beta.create_namespaced_deployment(
body=dep, namespace="default")
print("DEPLOYMENT {0} created. status={1}".format(filename, resp.metadata))
return resp
def getDeploymentList(self):
resp = self.__k8s_beta.list_deployment_for_all_namespaces()
return resp
def deleteDeployment(self, name, namespace="default", **kwargs):
body = kubernetes.client.V1DeleteOptions(**kwargs)
resp = self.__k8s_beta.delete_namespaced_deployment(name, namespace, body, **kwargs)
return resp
def deleteAllDeployment(self, namespace="default", **kwargs):
respd = self.__k8s_beta.delete_collection_namespaced_deployment(namespace)
respr = self.__k8s_beta.delete_collection_namespaced_replica_set(namespace)
respp = self.__k8s_core.delete_collection_namespaced_pod(namespace)
return respd,respr,respp

def createService(self, filename, filedir=None):
if filedir is not None:
filename = os.path.join(filedir, filename)
with open(filename) as f:
svc = yaml.load(f)
resp = self.__k8s_core.create_namespaced_service(
body=svc, namespace="default")
print("SERVICE {0} created. status={1}".format(filename, resp.metadata))
return resp
def getServiceList(self):
resp = self.__k8s_core.list_service_for_all_namespaces()
return resp
def deleteService(self, name, namespace="default", **kwargs):
resp = self.__k8s_core.delete_namespaced_service(name, namespace, **kwargs)
return resp

kd = KubeDeployer()
kdeploy = kd.createDeployment("nginxdep.yml", "/opt/workdir/kube/")
ksvc = kd.createService("nginxsvc.yml", "/opt/workdir/kube/")

それではKubernetesでJobsを起動します。

kubectl create -f deployjob.yml 

まずはKubernetes JobsがPodsとともに起動します。

2018-02-04_3.PNG

しばらくするとnginxのDeploymentとServiceが起動します。

2018-02-04_4.PNG

これでnginxも起動して公開されました。

2018-02-04_2.PNG


おわりに

PythonからDockerとKubernetesを使い、DockerからDockerとKubernetesを使い、KubernetesからKubernetesを使う方法を紹介しました。

個人的にはInfrastructure as CodeをKubernetes上から行いたい(Kubernetesの管理もKubernetesにまとめたい)という欲求で調べてみましたが、意外とうまくいって良かったです。

DockerもKubernetesも複雑ではありますが、慣れると楽しいですよね。

なお、今回デプロイした環境はすべて削除済みなので悪しからず。


参考

https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md

http://docker-py.readthedocs.io/en/stable/index.html