Amazon EC2でCentOSにKubernetesを構築する（後編）

# はじめに

- [Amazon EC2でCentOSにKubernetesを構築する（前編）　](https://qiita.com/rawr/private/b4446ef8314109a270ca)
Kubernetesのインストール～Dashboard導入まで

- Amazon EC2でCentOSにKubernetesを構築する（後編）　ここ
Dockerイメージの作成～サービスの公開まで

今回はKubernetes上でサービスを公開したいと思います。構築方法については前編を見てください。

# Kubernetesコンポーネントの簡単な説明

最初に、今回出てくるコンポーネントについて簡単に説明しておきます。

- Pod
１つ以上のコンテナーをまとめたもの。Kubernetesがデプロイする単位であり、Kubernetesが管理する最小単位。「１つ以上」となっているのは、複数のコンテナーをアプリケーション要件から、必ず同一ノードで実行しなければならない場合のためです。

- Service
０個以上のPodをまとめて、内部のPodの配置や内容に関わらず外部に同じ方法でアクセスするエンドポイントを提供するもの。内部のPodに対して、ロードバランシングをしたりFQDNを設定したりサービス検索（ディスカバリ）を提供したりします。０個PodのServiceという、いわゆる空のServiceに何の価値があるのか、と思うかもしれませんが、これは外部のリソースを参照したいときに使います。たとえば、Kubernetes外部にある構築済みのデータベースなどです。

- Deployment
アプリケーション(≒Pod)のリリースを行うもの。PodとServiceが静的なものを対象にするのに対して、Deploymentは動的なものを扱います。アプリケーションをリリースするとき(Podをデプロイするとき)、デフォルトでは、新しいアプリケーションをデプロイする → Podを起動する → FQDNのあて先(IPアドレス)を変更する → 古いPodを停止する、というように行うが、その処理を定義できる。

PodとServiceの関係

![2018-05-02_145857.png](https://qiita-image-store.s3.amazonaws.com/0/208281/e8d0fa67-20fb-0cf5-1d50-4a2fc8d3ee1c.png)

Deploymentのイメージ

![2018-05-07_104018.png](https://qiita-image-store.s3.amazonaws.com/0/208281/cbff5615-31c6-9e21-9df3-bd98760811a3.png)

![2018-05-07_104057.png](https://qiita-image-store.s3.amazonaws.com/0/208281/d47bf4e7-0fd0-fc15-666c-8cbabf493fd0.png)

![2018-05-07_104130.png](https://qiita-image-store.s3.amazonaws.com/0/208281/36c10635-ceac-c58c-3aaf-15b7064aa263.png)

# 環境

サーバーが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ノード２に配置されました。 また、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 でアクセスしてみます。

![2018-05-02_124134.png](https://qiita-image-store.s3.amazonaws.com/0/208281/5e5cd79f-8f8a-7d1d-d94b-edbe6f8aa6b3.png)

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インスタンスは、１インスタンスにつきIPアドレスが１つしか付与できないからです（だから、仮想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は、次のようになります。

```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イメージに変更するだけです。

```yaml
- 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も保持しているため、戻すことができます。

１つ前に戻すには、次のコマンド１つを実行するだけです。

```
# kubectl rollout undo deployments my-httpd
```

ロールアウトの履歴を見ることもできます。

```
# kubectl rollout history deployments my-httpd
REVISION  CHANGE-CAUSE
2         <node>
1         <none>
```

ロールアウトをロールバックすると、この履歴が１つ減るのではなく、さらに１つ追加されます。Gitで言うrevertをcommitするイメージでしょうか。そのため、もう一度ロールバックすると、さらに１つ前のバージョンになるのではなく、最初のバージョンになってしまいます。

特定のバージョンにロールバックしたい場合は、`--to-revision`で番号を指定します。

```
# kubectl rollout undo deployments my-httpd --to-revision=(REVISIONの番号)
```

# まとめ

これで何とか最低限のことができましたが、今回話ができたのはKubernetesのほんの一部の機能でしかありません（それでもQiita２回分になりました）。WebサービスはWebアプリケーションだけではなく、データベースも重要かと思います。データベースはWebアプリケーションと異なりステートフルのため、Webアプリケーションと同じように扱えません。Kubernetesはデータベースを扱うStatefulSetというコンポーネントも実装され（1.6ではexperimentalでしたが、1.9でGAになりました）、これも１つの大きな話になります。今回話したPod～Service～Deploymentもまだ一部しか話できていません（内部の仕組みの話は全く書けていませんし）。Kubernetesはいまや大きなプロダクトとなっているため、情報がまとめやすい書籍の出版が待ち望まれるところです。私？　書きませんよ？

# 参考文献

- [マニュアル(インストール)](https://kubernetes.io/docs/setup/independent/install-kubeadm/)
- [マニュアル(デプロイ)](https://kubernetes.io/docs/user-journeys/users/application-developer/foundational/#section-2)
マニュアルは英語ですが、一度は目を通しておいたほうが良いでしょう。

- [RedHat7上に構築](https://kazuhisya.netlify.com/2018/02/08/how-to-install-k8s-on-el7/)
メモ書きレベルですが、UbuntuではなくRedHat7上で、かつKubernetes最新版の記述になっています。

- [flannel 0.90ではバグあり](https://qiita.com/hichihara/items/79ef6613026f8c13eb99)
私は最初から0.91なのでハマりませんでしたが、大きなバグがあるようです。

- [Kubernetesの本](https://www.amazon.co.jp/%E5%85%A5%E9%96%80-Kubernetes-Kelsey-Hightower/dp/4873118409/ref=sr_1_1?s=books&ie=UTF8&qid=1525144350&sr=1-1&keywords=kubernetes)
O'ReillyからKubernetesの本の日本語訳がでました。内容は1.6ベースになっているようで、1.8や1.9の機能であるRoleやMulti Masterの記述はありませんでした。