はじめに
- 
Amazon EC2でCentOSにKubernetesを構築する(前編) 
 Kubernetesのインストール~Dashboard導入まで
- 
Amazon EC2でCentOSにKubernetesを構築する(後編) ここ 
 Dockerイメージの作成~サービスの公開まで
今回はKubernetes上でサービスを公開したいと思います。構築方法については前編を見てください。
Kubernetesコンポーネントの簡単な説明
最初に、今回出てくるコンポーネントについて簡単に説明しておきます。
- 
Pod 
 1つ以上のコンテナーをまとめたもの。Kubernetesがデプロイする単位であり、Kubernetesが管理する最小単位。「1つ以上」となっているのは、複数のコンテナーをアプリケーション要件から、必ず同一ノードで実行しなければならない場合のためです。
- 
Service 
 0個以上のPodをまとめて、内部のPodの配置や内容に関わらず外部に同じ方法でアクセスするエンドポイントを提供するもの。内部のPodに対して、ロードバランシングをしたりFQDNを設定したりサービス検索(ディスカバリ)を提供したりします。0個PodのServiceという、いわゆる空のServiceに何の価値があるのか、と思うかもしれませんが、これは外部のリソースを参照したいときに使います。たとえば、Kubernetes外部にある構築済みのデータベースなどです。
- 
Deployment 
 アプリケーション(≒Pod)のリリースを行うもの。PodとServiceが静的なものを対象にするのに対して、Deploymentは動的なものを扱います。アプリケーションをリリースするとき(Podをデプロイするとき)、デフォルトでは、新しいアプリケーションをデプロイする → Podを起動する → FQDNのあて先(IPアドレス)を変更する → 古いPodを停止する、というように行うが、その処理を定義できる。
PodとServiceの関係
Deploymentのイメージ
環境
サーバーがIPアドレスとなっていて分かりにくいので、載せておきます。
- サーバー構成
| 名前(兼hostname) | 役割 | IPアドレス | 
|---|---|---|
| kube-master | Masterノード | 172.26.22.85 | 
| kube-node1 | Workerノード1 | 172.26.22.71 | 
| kube-node2 | Workerノード2 | 172.26.22.99 | 
| docker-repo | Docker private repository | 172.26.22.6 | 
Dockerイメージを作成してからServiceを公開する
デプロイするWebコンテナーは何でもいいのですが、この記事では例として Apache HTTPD にしました。もちろんNginxでも問題ありませんし、手順も全く同じです。
Dockerイメージを作成
dockerレポジトリーから Apache HTTPDのイメージをpullして、前編で作成した private repository(172.26.22.6)へpushしておきます。KubernetesはPodをデプロイするとき、常にイメージを docker pull するため、ローカルにイメージを置いておくだけではうまくいきません。またpushするときは、必ずtagを付けておく必要があります。
# docker pull docker.io/centos/httpd
...(snip)...
# docker run --privileged -d -p 80:80 --name my-httpd docker.io/centos/httpd /sbin/init
# docker exec -it my-httpd /bin/bash
$ systemctl enable httpd & systemctl start httpd
$ logout
# docker commit my-httpd 172.26.22.6:5000/my-repo/my-httpd:v0.01
# docker push 172.26.22.6:5000/my-repo/my-httpd:v0.01
dockerコンテナーの起動の仕方は、この記事ではコンテナー内に入って systemd 経由で行ったため特権モード (--privileged) を付けましたが、/usr/sbin/httpdを直接起動する方法でも良いです。
Deploymentの作成
Deploymentを作ってPodをデプロイします。
# kubectl run my-httpd \
--image=172.26.22.6:5000/my-repo/my-httpd:v0.01 \
--port=80 \
--labels="app=my-sample"
Serviceを作成
作成したDeploymentに対して、Serviceを作成します。
# kubectl expose deployment my-httpd 
確認
まず、Podが起動できているかを確認してみましょう。
# kubectl get pods -o wide
NAME                             READY     STATUS    RESTARTS   AGE       IP             NODE
my-httpd-55cb77848d-d5bvl        1/1       Running   0          20s       10.244.2.169   kube-node2
STATUS が Running になっていれば成功です。Kubernetesは、Podをどのノードに配置するかは勝手に決定します(どのノードに配置するかは、コントロールするものではありません)。今回は NODE が kube-node2 になっているため、Workerノード2に配置されました。 また、Pod単位に内部ネットワークのIPアドレスが割り振られます(IPの列)。
次に、Serviceを見てみます。
# kubectl get services -o wide
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE       SELECTOR
my-httpd      ClusterIP   10.97.83.136     <none>        80/TCP           3s        app=my-sample
PORT(S) の 80/TCP は内部ネットワークのLISTENポートであり、まだ外部に公開されていません。この段階で画面を確認するには、SSHポート転送を使ってアクセスします。ローカルPCの 80 ポートを Masterノード経由で Masterノードのlocalhost:80にポート転送し、ブラウザから http://localhost でアクセスしてみます。
It works! ではなく、Testing 123... が表示されると思います。
Serviceを外部に公開する
Serviceを外部のポートに転送しましょう。先ほど作成したServiceの設定を編集します。
# kubectl edit services my-httpd
spec.type の値を ClusterIP から NodePort に変更して保存します。
Serviceを見てみましょう。
# kubectl get services -o wide
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE       SELECTOR
my-httpd      NodePort    10.97.83.136     <none>        80:31419/TCP     1h        app=my-sample
PORT(S)が80:31419/TCPに変わっています。これは、内部ネットワークの80ポートに対して、外部ネットワークの31419ポートから転送されます。ポート番号はKubernetesが自動で割り振ります。これで、SSHポート転送をしなくてもブラウザからアクセスできるようになります。ブラウザからMasterノード(172.26.22.85)に対して直接 http://172.26.22.85:31419/ でアクセスすると、再び Testing 123... が表示されると思います。
Serviceにpublic IPを割り振る (できない)
ユーザーがWebサービスにアクセスするのに、:31419のようなポート番号を付けるのはおかしいので、プロキシーをしてくれるロードバランサーをServiceに設定しましょう。再びServiceを編集します。
# kubectl edit services my-httpd
spec.type の値を NodePort から LoadBalancer に変更して保存します。Serviceを見てみます。
# kubectl get services -o wide
NAME          TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE       SELECTOR
my-httpd      LoadBalancer   10.97.83.136     <pending>     80:31419/TCP     1h        app=my-sample
public IPが割り振られえると(時間が掛かりますが)、EXTERNAL-IP にIPアドレスが表示されます。しかしEC2の場合は <pending> のままpublic IPが割り振られません。これはEC2インスタンスは、1インスタンスにつきIPアドレスが1つしか付与できないからです(だから、仮想IPも作れない)。仕方がないので、EC2の場合はNodePortで動かすしかありません。Serviceの設定を編集して spec.type の値を LoadBalancer から NodePort に戻しておきましょう。
設定をManifest(YAML)にしておく
DeploymentとServiceの作成をコマンドで行ってしまいましたが、今後の運用のために、設定はYAML(かJSON)で書いたほうが良いです。将来はKaaS(Kubernetes as a Service)が提供するコンソール画面やDashboardから、ボタンクリックでできるようになると思いますが、それはまだまだ遠い未来の話であり、今はYAML(かJSON)で頑張るしかありません。
現在設定されている Pod や Service といった情報のYAML化は、次のコマンドで可能です。
(Podの場合)
# kubectl get pods my-httpd-55cb77848d-d5bvl -o yaml
(Deploymentの場合)
# kubectl get deployments my-httpd -o yaml
(Serviceの場合)
# kubectl get services my-httpd -o yaml
今回作ったmy-httpdのYAMLは、次のようになります。
### deployment
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: my-sample
  name: my-httpd
#  namespace: my-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-sample
  template:
    metadata:
      labels:
        app: my-sample
    spec:
      containers:
      - image: 172.26.22.6:5000/my-repo/my-httpd:v0.01
        imagePullPolicy: IfNotPresent
        name: my-httpd
        ports:
        - containerPort: 80
          protocol: TCP
---
### service
apiVersion: v1
kind: Service
metadata:
  labels:
    app: my-sample
  name: my-httpd
#  namespace: my-test
spec:
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: my-sample
  sessionAffinity: None
  type: NodePort
---
コンテナーの新バージョンをリリースする
アプリケーションは一度リリースしたらそれっきり、ということはなく新しい実装やバグ修正などでリリースがあると思います。
リリースする
Deploymentを使うと、リリースは簡単です。先のYAML内にある、spec.template.image の値を、新しいバージョンのDockerイメージに変更するだけです。
- image: 172.26.22.6:5000/my-repo/my-httpd:v0.01
 ↓
- image: 172.26.22.6:5000/my-repo/my-httpd:v0.02
Kubernetesに適用します。
# kubectl apply -f (YAMLファイル名)
これだけで自動的にロールアウトが実行されます。
リリースを取り消す(ロールバックする)
リリースしたアプリケーションに問題があったり、Dockerイメージに問題がありコンテナーが起動できなかったときなど、リリースを戻したいときがあります。Kuberentesは古いバージョンのDeploymentも保持しているため、戻すことができます。
1つ前に戻すには、次のコマンド1つを実行するだけです。
# kubectl rollout undo deployments my-httpd
ロールアウトの履歴を見ることもできます。
# kubectl rollout history deployments my-httpd
REVISION  CHANGE-CAUSE
2         <node>
1         <none>
ロールアウトをロールバックすると、この履歴が1つ減るのではなく、さらに1つ追加されます。Gitで言うrevertをcommitするイメージでしょうか。そのため、もう一度ロールバックすると、さらに1つ前のバージョンになるのではなく、最初のバージョンになってしまいます。
特定のバージョンにロールバックしたい場合は、--to-revisionで番号を指定します。
# kubectl rollout undo deployments my-httpd --to-revision=(REVISIONの番号)
まとめ
これで何とか最低限のことができましたが、今回話ができたのはKubernetesのほんの一部の機能でしかありません(それでもQiita2回分になりました)。WebサービスはWebアプリケーションだけではなく、データベースも重要かと思います。データベースはWebアプリケーションと異なりステートフルのため、Webアプリケーションと同じように扱えません。Kubernetesはデータベースを扱うStatefulSetというコンポーネントも実装され(1.6ではexperimentalでしたが、1.9でGAになりました)、これも1つの大きな話になります。今回話したPod~Service~Deploymentもまだ一部しか話できていません(内部の仕組みの話は全く書けていませんし)。Kubernetesはいまや大きなプロダクトとなっているため、情報がまとめやすい書籍の出版が待ち望まれるところです。私? 書きませんよ?
参考文献
- 
マニュアル(デプロイ) 
 マニュアルは英語ですが、一度は目を通しておいたほうが良いでしょう。
- 
RedHat7上に構築 
 メモ書きレベルですが、UbuntuではなくRedHat7上で、かつKubernetes最新版の記述になっています。
- 
flannel 0.90ではバグあり 
 私は最初から0.91なのでハマりませんでしたが、大きなバグがあるようです。
- 
Kubernetesの本 
 O'ReillyからKubernetesの本の日本語訳がでました。内容は1.6ベースになっているようで、1.8や1.9の機能であるRoleやMulti Masterの記述はありませんでした。





