2016年9月 2日

基盤技術

KubernetesとOpenStackを連携させる方法

  • このエントリーをはてなブックマークに追加

OpenStackの運用エンジニアをしている木下です。
今回はマルチプラットフォームCI環境に関する連載の第4弾として、KubernetesとOpenStackの連携を検証した際に分かった、動作させる手順と現在の状況について説明していきたいと思います。
本エントリーでは、「Kubernetesの認証としてKeystoneを利用する方法」、「KubernetesからCinderを利用する方法」の2つを詳しく解説していきます。
本エントリーで検証したKubernetesのバージョンは1.2.2になります。

Kubernetesの認証としてKeystoneを利用する

Kubernetesの認証・認可の機構に関して簡単に説明したあと、Keystone連携の設定方法やその動作について紹介していきます。

Kubernetesの認証・認可

認証・認可はさまざまな方法が利用でき、それだけで非常にボリュームのある内容になるので、ここでは流れのみを紹介させていただきます。
ユーザーがKubernetesのリソースを操作するまでには、次のような流れを経る事となります。



図1. Kubernetesが提供する認証・認可

ユーザーからCLIなどを通して要求があると、クラスタを利用可能なユーザーであるかを認証し、そのユーザーが操作可能な範囲を認可で確認します。
最後にAdmission Controlでリソース制限を超えていないかなどをチェックしたうえでユーザーはリソースを操作可能になります。

1つのKubernetesクラスタを複数のグループで利用する場合、他のグループが作成したpodやserviceなどのリソースを勝手に削除・編集できてしまうと問題がありますので、認証と認可を適切に設定する必要が出てきます。

Kubernetesの認証にKeystoneを利用する

具体的に設定方法や動作に関して説明していきます。

Keystone連携の設定方法

Keystone認証を有効にする方法は非常に簡単です。下記のオプションをkube-apiserverに持たせるだけです。

--experimental-keystone-url=[AuthURL]

ただし、ここで[AuthURL]はhttpsである必要があります。
httpのものを設定しようとすると、下記のようなエラーがkube-apiserverのログに出力されます。

Invalid Authentication Config: Auth URL should be secure and start with https

また、kube-apiserverに与える他のオプションにも注意してください。
次のオプションが認証と関係しており、設定によってはいかなる場所からのアクセスも素通りさせてしまうことになってしまいます。

--insecure-bind-address=127.0.0.1: The IP address on which to serve the --insecure-port (set to 0.0.0.0 for all interfaces). Defaults to localhost.
--insecure-port=8080: The port on which to serve unsecured, unauthenticated access. Default 8080. It is assumed that firewall rules are set up such that this port is not reachable from outside of the cluster and that port 443 on the cluster's public address is proxied to this port. This is performed by nginx in the default setup.

Keystone連携されたKubernetesクラスタを利用してみる

実際にKeystone認証がかかったKubernetesのAPIサーバへアクセスした際の挙動を紹介します。

kubectlコマンドの利用方法に関する詳細は、次のドキュメントを参考にしてみてください。
http://kubernetes.io/docs/user-guide/kubectl-overview/

kubectlコマンドを実行すると下記のようにUsernameとPasswordを尋ねられます。

$ kubectl get node --server https://apiserver
Please enter Username: kinoshita
Please enter Password: XXXXXXX
Please enter Username: kinoshita
Please enter Password: XXXXXXX

NAME       STATUS                        AGE
ctrl-001   Ready,SchedulingDisabled      17d
node-001   Ready                         17d
node-002   NotReady,SchedulingDisabled   17d
node-003   Ready                         17d
node-004   Ready                         17d
node-005   Ready                         17d

OpenStackのpython-clientと同じような感覚で利用することができます。
UsernameとPasswordはset-credentialsコマンドを利用して設定しておくことで入力を省くことができます。

$ kubectl config set-credentials kinoshita --username=kinoshita --password=$pass
$ kubectl get node --user kinoshita --server https://apiserver

ここで上記の設定値は~/.kube/configに保存されます。
複数のKubernetesクラスタを利用する場合は、contextを設定し切り替えて利用するのが良いと思われます。

また、認可の部分では、ABACの機能を利用するとこでnamespaceごとに操作可能なリソースを制限することができます。事前に次のようなPolicyを設定してkube-apiserverを起動すれば、OpenStackと同様のtenant isolationが実現可能です。

kube-apiserverの起動引数に下記を追加。

--authorization-mode="ABAC"
--authorization-policy-file="/etc/kubernetes/policy.jsonl"

policy.jsonlに次のように記載。

{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"*", "apiGroup": "*", "resource":"*", "nonResourcePath": "*", "readonly": true}}
~~ 省略 ~~
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kinoshita","namespace": "siteop-pc-team", "resource": "*","apiGroup": "*"}}

一行目ですべての権限を絞り、以降の行で必要なアクセスに関しては許可していく表記になっています。
上記では一部省略しており、Kubernetesのシステムユーザー(kubeletやproxyなど)が必要なリソースへアクセスできるようにする行を書く必要があります。

policyまで設定された状態で、自分の所属しないnamespaceへのアクセスをしようとすると次のように認可の部分で弾かれていることがわかるかと思います。この例では、kinoshitaというユーザーはkube-systemというnamespaceへのアクセスをpolicyファイルの中で許可されていないので弾かれています。

% kubectl get pod --server https://apiserver --namespace kube-system
Please enter Username: kinoshita
Please enter Password: XXXXXXX
Please enter Username: kinoshita
Please enter Password: XXXXXXX
Error from server: the server does not allow access to the requested resource (get pods)

使ってみると…

一つのKubernetesクラスタを複数のユーザーで共有することを考えた場合、そのユーザーをどのように管理するかは難しい問題でした。
しかしながら、Keystoneが認証へそのまま利用できるとなると、OpenStack環境があればユーザーの管理も既存の環境をそのまま流用できるので追加の認証基盤が不要になります。
特にヤフーが持つKeystoneはすでに社内の基盤との連携がされているため、ユーザー追加やパスワードの変更といったフローも含めて外出しができます。
設定も簡単な上に、KubernetesのnodeがOpenStackが払いだしたVMで無い場合でも利用できるので、Keystoneだけをセットアップし社内のLDAPやActiveDirectoryと連携させて運用するというのも選択肢として考えられるかと思います。

KubernetesとCinderを連携させる

続いては、KubernetesとCinderを連携させる方法について解説いたします。
Cinderの用途としては、KubernetesにおいてPodに対して永続的なストレージを提供するPersistent Volumeのバックエンドとして利用するということになります。

まずは、Persistent Volumesについて簡単に説明し、その後連携方法、利用の流れなどを説明していきます。

Persistent VolumesとDynamic Provisioning

KubernetesにおいてPodは、生き死にを繰り返すものです。Podが持つ情報はPodが死んだときに消滅してしまいます。
そこで、KubernetesではPersistent Volumesを提供することで、Podのライフサイクルとは独立した永続的なストレージを実現しています。

Persistent Volumesはpluginベースで実装されており、次の中から選択して利用することになります。

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • Glusterfs
  • HostPath (single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
  • OpenStack Cinder

基本的な利用の流れとしては、

  1. Kubernetesの管理者がPersistentVolume(PV)を作成します。ここで容量、ReclaimPolicy、どのpluginを利用するかなどを選択します。
  2. Kubernetesの利用者がPersistentVolumeClaim(PVC)を作成します。ここでは、利用者が必要なサイズのみをyaml内で宣言する形になります。
  3. KubernetesがPVとPVCを紐付けて利用可能な状態にします。ここでPVCがPodへマウント可能な状態となります。
  4. PVC削除時にはPVとの紐付けは解除され、PVはそのReclaimPolicyに従ってデータの削除やPV自身の削除が行われます。

おおまかには上記のように、管理者がストレージを事前に準備し、利用者が必要な容量を宣言して使うといった流れになります。

このフローでは、必要になると想定されるPVを事前に準備しておくといった、手動オペレーションが必要になってきます。
そこで、Kubernetesでは一部のpluginにおいてDynamic Provisioningをサポートしています。
Dynamic Provisioningでは、PVCの作成時に自動でPVを作成し、紐付けを行ってくれます。しかしながら、対応するpluginは現在のところ次の3つのみになります。

  • OpenStack Cinder
  • AWS EBS
  • GCE Persistent Disk

将来的には、Glusterfsもサポートされるようで、現在プルリクエストが出ています。
Persistent Volumesの詳しい説明は次のdocumentや、KubeCon2016の資料に記載されています。
http://kubernetes.io/docs/user-guide/persistent-volumes/
//www.slideshare.net/kubecon/kubecon-eu-2016-kubernetes-storage-101

Persistent VolumesでCinderを利用する方法

それでは、Persistent Volumesの利用時に、Cinderからボリュームを払い出してもらう方法について解説していきます。

設定方法

まず、前提としてKubernetesのスケジューラブルなnodeがOpenStackのVMとして稼働している必要があります。
Cinderで作成されたボリュームがnode(VM)へアタッチされ、結果的にPodから利用可能な状態になるためです。

Kubernetes側の設定は、Keystoneの場合と同様に非常に簡単です。
kube-apiserver、kube-controller-manager、kubeletの起動引数に次のオプションを付与します。

--cloud-provider=openstack --cloud-config=/etc/kubernetes/openstack.conf

ここでopenstack.confの中身は次のように記述します。

[Global]
auth-url = [AuthURL]
username = headless.siteop
password = XXXXXXXX
tenant-name = siteop-pc-team

利用しているOpenStackのテナントですが、これはnodeとして稼働しているVMを持つテナントと一致している、もしくはAdminである必要があります。
Kubernetes内部の挙動として、kubeletが起動した際にそのサーバ上の/var/lib/cloud/data/instance-idを参照し、自身のOpenStack上のUUIDを知り、
そして、openstack.confに書かれたユーザー情報によりkubeletがCinderボリュームと自身のVMのアタッチ処理を行うためです。
kubeletの場合は以下のようにOpenStackとの連携が成功したという旨のログが出力されています。

kubelet[25428]: I0711 21:53:46.009275   25428 openstack.go:186] Got instance id from /var/lib/cloud/data/instance-id: c467119f-68a7-4ade-b56f-65ea4379e074
kubelet[25428]: I0711 21:53:46.009349   25428 server.go:312] Successfully initialized cloud provider: "openstack" from the config file: "/etc/kubernetes/openstack.conf"

Cinder連携されたPersistent Volumesを利用してみる

Persistent VolumesのpluginとしてCinderを利用する準備は整いました。
それでは実際にPVCを作成して、永続的なストレージが利用できるか確認していきます。

OpenStack CinderではDynamic Provisioningがサポートされているため、利用者がPVCを作成すると自動でCinderでのボリューム作成とPVの作成、PVとPVCの紐付けまでが行われます。

今回はnginxで利用するPVCを作成するという前提で進めてみます。
PVCを作成するためのyamlは次のようになります。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
  annotations:
    volume.alpha.kubernetes.io/storage-class: foo
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

yamlを見てわかる通り特にCinderを利用するという旨は書いていません。
Kubernetes自身がOpenStackと連携されているという状況を知っており、自動でCinderに対してボリューム作成のリクエストを投げます。
ここで、OpenStackを確認してみるとKubernetesが自動で作成してくれたボリュームが存在しているのがわかります。



図2. 自動で作成されたCinderボリューム

kube-controller-managerのログを確認すると下記のようにCinderに対してボリュームを作成する命令をしていることがわかります。

E0705 12:47:53.069590       5 persistentvolume_provisioner_controller.go:124] Error reconciling volume pv-cinder-6w4yr: PersistentVolumeClaim[default/nginx-pvc] not found in local cache
I0705 12:48:47.622042       5 openstack.go:1079] Created volume 6cc130c2-12fc-4d3a-b9b5-f88a082229b6
I0705 12:48:47.622110       5 cinder_util.go:159] Successfully created cinder volume 6cc130c2-12fc-4d3a-b9b5-f88a082229b6

また、Kubernetes上でPVおよびPVCの作成状況を確認すると下記のように作成済みでPVとPVCの紐付けも完了しています。

$ kubectl get pv
NAME              CAPACITY   ACCESSMODES   STATUS    CLAIM               REASON    AGE
pv-cinder-je73f   3Gi        RWO           Bound     default/nginx-pvc             22h

$ kubectl get pvc
NAME        STATUS    VOLUME            CAPACITY   ACCESSMODES   AGE
nginx-pvc   Bound     pv-cinder-je73f   3Gi        RWO           22h

あとは、これを利用するPodを起動してみましょう。

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - name: http
              containerPort: 80
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: nginx-pvc

Podも無事に作成されました。

$ kubectl get pod
NAME                       READY     STATUS         RESTARTS   AGE
nginx-i8otk                1/1       Running        0          3h

OpenStack上でもPodが存在するnode(VM)に対して先ほどのボリュームがアタッチされているのがわかります。



図3. アタッチされたCinderボリューム

Podの内部へ入って状況を確認してみてもマウントされていることがわかります。

$ kubectl exec -it nginx-i8otk df
Filesystem           1K-blocks      Used Available Use% Mounted on
~~ 省略 ~~
/dev/vdb               3030800      9216   2847916   0% /usr/share/nginx/html

/dev/vdbが指定したディレクトリに対してマウントされていることが確認できます。

対象のPodをdeleteして再作成すると、一度node(VM)からデタッチされて、再作成先でアタッチされることも確認できます。

使ってみると…

Persistent Volumesの通常のフローとは異なりDynamic Provisioningがサポートされており、PVCの作成のみで完結するため手動のオペレーションを省略することができます。
また、Kubernetes側でCinderボリュームの作成~ファイルシステムの作成~アタッチ・デタッチまでを自動で操作するため、利用者としてはバックエンドストレージの存在を気にする必要がなくなっています。

欠点としてはNFSではないため、複数のPodから一つのPVCを同時に参照できない点が考えられます。こちらは、現状としてどうしても必要な場合はNFSのpluginを利用するしか手はなさそうです。
現在は、OpenStack, AWS, GCEの場合のみがDynamic Provisioningがサポートされていますが、今後はGlusterFSなどもサポートされるようですので、物理マシンをnodeとして利用する場合も同様に利用できることが期待できます。

まとめ

今回の連載では、KubernetesをOpenStackと連携させることでKeystoneの認証機能や、CinderによるPersistent Volumesが利用できることを紹介しました。
OpenStack上にKubernetesを構築した場合も、GCEやAWS上に構築した場合と変わらずにさまざまな機能を利用できます。また、OpenStack側で特別な設定をする必要はなく、既存の環境をそのまま利用することができます。Kubernetesが自身の稼動環境を抽象化してくれるので、利用者は稼動環境がOpenStackなのかAWSなのか意識しなくて済むようになります。
今回は紹介しませんでしたが、OpenStackのLBaaSをKubernetesから利用可能であることや、OpenStack KuryrがKubernetesをサポート予定であることを考えると、今後OpenStackとKubernetesのさらなる連携が進むことは期待できるかと思います。

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加