LoginSignup
12
13

More than 5 years have passed since last update.

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

Posted at

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を操作します。
このあたりは道楽で試してみたらできたことを共有するだけです。

今回書いたコード

やること

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

ホスト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

12
13
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
12
13