コンテナの話をはじめから 〜Kubernetesを本番運用して5年目の所感まで〜

この記事は、ベルフェイスの有志メンバーでつなぐ [新年ベルリレー](https://bs.bell-face.com/2021/01/04/2021010401/ "新年ベルリレー") 20日目のコンテンツです。

# はじめに
ベルフェイスでは、本番サービス運営のためにコンテナ技術 ([Kubernetes](https://kubernetes.io/ja/ "Kubernetes") = クーバネティス) を利用しています。

本項の執筆者 [@numa-numa](https://qiita.com/numa-numa) は転職して日が浅いのですが、以前からKubernetes (これより先は、主にK8sと記載します) を本番で運用してきました。
私にとってコンテナで商用サービスを扱うようになってもうすぐちょうど5年…という節目の年でもあるので、コンテナのことを基本からまとめながら、K8sの何が良いのか/悪いのかという経験に基づいた概観についてまでご紹介する内容にしていきたいと思います。

# 【歴史】 2000年代半ばくらいから一気に隆盛を極めた仮想技術

いわゆる、コンピュータの仮想化と言われる技術が多用されるようになったのは、2000年代半ば (2005年前後?) が黎明期であり、それから数年の間に一気に多用されるようになりました。

コンピュータを仮想化するようになった背景要因として、ハードウェアの高性能化が挙げられます。
WEBサービスを提供する時に、フロント系サーバ群にはWEBサーバ (Apacheなど) や開発言語 (主にPHPを利用したものやRailsなどの各種フレームワーク) などをインストールして多数台の冗長構成で利用することが一般的です。

ただ、徹底したチューニングを施したとしても、OSの制約などでサーバ1台で捌けるアクセス量もある程度上限が決まってきます。
サーバリソースは使えるだけ有効に使いたいものですが、台数を増やす前にサーバ1台の性能が大きすぎて非効率…という状況も往々にして発生していました。

そこで、物理ベースのサーバには仮想化のソフトウェアをインストールすることで、1台の物理サーバのリソースを分割し何台かのサーバを内部に収容するタイプの仮想技術が使われるようになりました。

イメージ的には以下の図のような構成概念になります。
![hypervisor_diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147015/254b159a-439c-cc30-7199-59e8b4a2cf4d.jpeg)
これは、細分化すると、

- ホストOS型 (VMware Fusion, Vagrant, VirtualBox など)  
=>　OSはWindow/Linuxなどを利用し、仮想化用ツールをインストール
- ハイパーバイザ型 (Esxi, KVM, Xen など)  
=>　OS自体が仮想化専用のもの

の2通りがあります。
ホストOS型は主にPCで使われる技術で本項の主旨とも外れるので、ここではサーバ用途に限定しハイパーバイザ型だけを図示しています。

# 【歴史＋α】 コンテナとは何か？

さて、私が使ってきたK8sをはじめとしたコンテナ技術は仮想化技術です。

これがいわゆるハイパーバイザ型と何が違うのかというと、Linuxのコンテナ層というOSのカーネルの機能を利用して仮想化を行っている点です。
具体的には、 **namespaces** と **cgroup** という機能を利用しています。詳細は割愛しますが、これらはK8sの動作についても重要な役割を持っています。

- **namespaces**　…　実行環境を名前空間として切り分けることで、コンテナ間の分離・独立性を確保する
- **cgroup**　…　CPU・メモリ・Disk I/Oなどにリソース制限を設ける

これらの特性を利用することで、Linuxという1つのOSでも実行空間分離やリソースへの制限を設けることで、仮想的に複数環境を動作することができる仕組みです。

コンテナ型の仮想化は、下の図のような構成概念となります。
![Container_diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147015/04dea8c7-720e-2877-7c55-ab06e0988420.jpeg)
Linuxにおけるコンテナ技術というのは比較的歴史があり、cgroupの元になるカーネル技術は2008年頃に開発されていて、当時からカーネル機能を利用したLXCという技術も開発されています。
その他、初期から独自のコンテナを展開してきたものとしてOpenVZというのがありますし、libvirt, systemd-nspawn, FreeBSD Jail など、実はいろいろなコンテナ技術があります…

しかしながら、**2021年初現在では、コンテナ ≒ Docker と言って差し支えない**昨今です。
https://www.docker.com/company

Dockerは2013年3月に急遽出てきた技術といえるでしょう。
それなのに、これが現在世界のコンテナの標準技術となっているのです。

Dockerがデファクトスタンダードになった理由は実際判然としないのですが、

- 単純に、市場のニーズに対するリリースタイミングがよかった
- 軽量さとシンプルさ、手軽な利用性
- 結果的に多くのLinuxディストリビューションに加え、Microsoft/Googleなどでも導入された

などではないかと、個人的には考えています。

- Control groupsの参考 (RHEL7)  
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_features
- LXCの参考 (技術評論社のベージ)  
https://gihyo.jp/admin/serial/01/linux_containers/0001
- OpenVZの参考 (公式)  
https://openvz.org/

# Kubernetesとは何か？

K8sはDockerを運用するためのオーケストレーションツールです。

Dockerはそれだけを使っても、スポットでだけ生存する単純な仮想環境 (コンテナ) を作成し、検証作業などに利用することしかできません。

WEBサーバ・DBサーバ・ストレージサーバ・KVS……
WEBサービスを商用展開する場合には、単一のコンテナがあれば良い訳ではなく、複数種のコンテナを立てて、それらを相互連携できるスキームが必要です。
このスキームを提供するのがオーケストレーションツールということになります。

**K8sはコンテナオーケストレーションツールのデファクトスタンダード**です。

Dockerの世界と違い、ことコンテナオーケストレーションに関しては、ここ数年はレッドオーシャンの状態が続いていました。
しかし、今はその戦いにひとまず決着がつき "K8sがデファクトだ！" と言い切ってほぼ問題ない状況だと考えています。
(ITのことなので、今後これがひっくり返らない保証もないのですが…)

# コンテナ (Docker) を使うと何がいいの？

結論からいうと、開発/運用における下記の5つの課題が解決されます。

1. **開発環境をすぐに立てることも、消すこともできる (環境の自由)**
1. **アプリケーションの実行環境 (多くはインフラ的な領域) の構成方法をコードとして定義することで、結果的に運用負荷が低減される**
1. **本番/ステージング/開発 各環境の差異がなくなり、アプリケーションリリースに対する心理的安全性が高まる**
1. **パッチ適用やカーネルアップデートが容易になる (それらがすぐ行えるかどうかの確認も含め)**
1. **CIを整備することで、安定的なデリバリーが確保される**

これらについて、単純な例でかんたんに説明します。

ここでは、WEBプログラムを用いない単純なWEBサーバをローンチすることを考えます。
Apacheをインストールし、テストページが見えればOKです:smile:

## 一般的なサーバの場合

当たり前ですが、もし物理サーバを用いるとすると、まずはハードウェアを用意し、そこにOSをインストールするキッティングから始まります:cyclone:
ただ、今はクラウドがありますので、私の方ではAWSを利用して Ubuntu がインストールされた VM(EC2) を立ててrootログインし、以下のようなコマンドを実行しました。

```
apt-get install -y apache2
systemctl start apache2
```

ブラウザを表示した結果は、以下の通りです。
![test_apache01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147015/411365b9-faf4-c255-7e1f-943a49477ed6.png)
OS標準のApacheテストページが見えるようになっています。

## Dockerの場合

Dockerではインフラ構成をコードにします。
このコード (Dockerfile) をもとにインフラ/アプリケーションをデプロイします。

通常、商用で利用するときは何らかのCIツールを使用することになりますが、ここではPC(Mac)でDockerfileを記載し、コマンドでコンテナを実行することにします。

Dockerfileは以下のような内容です。

```Dockerfile
FROM alpine:3.10

# Package Install
RUN apk update && apk add --no-cache apache2

# Run apache2
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
```

Dockerfileが書けたら、以下のようにコマンドを実行しDockerをビルド/実行します。

```
docker build -t test-apache ./
docker run --name test-apache -d -p 80:80 test-apache
```

こちらのブラウザ表示結果は、以下の通りです。
![test_apache02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147015/f0c65d70-b23d-4fae-a56f-d9d54d3a66f9.png)
ここでは Alpine というOSを利用しています。**Alpine は非常に軽量なため、一般にコンテナでの使用が推奨**されます。
OSなどが違っているので、テストページの見た目は全く異なっていますが、WEBサービスが構築できるという本質は同じです。

## どういい所につながる？

Dockerは **"定義(宣言)型インフラストラクチャ"** と言われます。
対して、ログインしコマンドコントロールするものは "命令型インフラストラクチャ" と言います。

命令型は、エンジニア (あるいはオペレータ) がコマンド操作を行うことで、自由に状態や構成を変化させることができます。
対して定義型は、コードによって定義された一定の状態を保ち続けることになります。

一見、前者の方が良いように感じますが、実は定義型を採用するだけで先に挙げた5つの課題解決に結びつくのです。

一つ一つ見ていくことにしましょう。

- **開発環境をすぐに立てることも、消すこともできる (環境の自由)**  
もし物理サーバでも構築しようものなら、破棄するときにはデータ消去とサーバ回収の業者まで呼ぶことになります。  
クラウドを利用する場合も、手動で立てたサーバを消し忘れれば、どんどんお金はかかります。  
　  
Dockerコンテナは、以下のようなコマンド1つで揮発します。  
(K8sの場合は別のコマンド体系を利用することになります。)

```
docker stop e3d51562fb72
```

- **アプリケーションの実行環境 (多くはインフラ的な領域) の構成方法をコードとして定義することで、結果的に運用負荷が低減される**
人がコンピュータに命令を行う＝運用作業…です。つまり、コンテナの利用範囲が広がれば広がるほど運用作業は少なくなります。

- **本番/ステージング/開発 各環境の差異がなくなり、アプリケーションリリースに対する心理的安全性が高まる**
開発だけインストールされているモジュールのバージョンが異なる…
ステージングだけサービス止まってた…
などは、WEBベンチャー企業を中心によくある話です。
コンテナを使うことで、こうしたことは起きなくなるか、相当起こりにくくなります。
どの環境も、同じDockerfileをもとに構成されるためです。

- **パッチ適用やカーネルアップデートが容易になる (それらがすぐ行えるかどうかの確認も含め)**
OSのカーネルアップデート。命令型のインフラでこれを行おうとすると、サービスの停止やサーバの切り離しを伴う数時間の作業が必要です。
そして、それら(本番)の作業に至るまでの検証作業も…
　
コンテナの場合。ここでは Alpine の3.10に特有の脆弱性があると仮定し、それが3.12で修正されたとします。
3.12でサービスが稼働するかの確認は、Dockerfileの先頭行を以下のように直してコンテナを作り直せばすぐにできます。

```Dockerfile
FROM alpine:3.12
```

- **CIを整備することで、安定的なデリバリーが確保される**  
先ほど実行したように、手動オペレーションでDockerイメージを実行するやり方はPCのみに通用するものです。
商用サービスへの適用検討すると、  

　・ビルド
　・テスト
　・デプロイ

　のフローを厳密化する必要が生じ、それを機にフローを設計(改善)することとなり、結果的に安定したデリバリー確保につながります。

# Kubernetesを使うと何がいいの？

まず、K8sはDockerのオーケストレーションツールですので、Dockerで享受できるいいことは同様に全て当てはまります。

K8sは非常に多機能であり多くのメリットがあるのですが、本項の内容に即した3点に絞ってご紹介します。

### 複数のDockerホストの管理

Dockerを1台のホスト(サーバ)で構成すると、故障などでホストが停止した時に全サービスが停止することになります。
ハイパーバイザ型の仮想化では、こうしたことがしばしば問題になってきました。
K8sでは複数のDockerホストを一元管理することによって、冗長性・高可用性を確保することが可能です。

### スケーリング/オートスケーリング

K8sでは、サーバ1台が **Pod** と呼ばれる単位で構成されます。
Podの数や性能は自由に定義できます。Podの性能を変更したいときは、コードを変更し再リリースを行うだけです。
アクセス量によってPod数が調整される、オートスケーリングも簡単に実装できます。

### オートヒーリング

Dockerではオペレーションを行うことでコンテナの内容が変わってしまった時に、再生成することで定義された状態に戻すことができます。
K8sではこれを一歩進め、Podに異常 (過大なアクセスによるリソース枯渇など) が起こりダウンしてしまった時にPodは自動で復旧されます。

# Kubernetesの構成概念

WEBにおいては、以下のようなものが基本となります。

K8sではさまざまな種類のコンテナをデプロイすることができるのですが、下図は **Deployment** と言われる種類のものを例に取っています。
Deployment は**WEBのフロントアクセス系に最も適したリソース**で、リリースを行うことで継続的にコンテナの更新を行うことが可能です。
![Deployment_diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147015/4dace1d2-fc28-58a3-03e7-3c95e89b2ff4.jpeg)

- **Pod**
実際にデプロイされるコンテナです。サーバ1台と同様のものと考えて差し支えありません。
Podはオートスケールで数を変更することもできますし、固定の数を指定しておくこともできます。
デプロイされるアプリケーションに応じて、CPU/メモリの割当て量を調整して構成します。

- **Namespace**
仮想のクラスタ空間であり、それぞれは分離されていて独立しています。
Namespaceをいくつか用意することで、複数のチームで利用したり、複数のアプリケーションを同じ(物理)K8sクラスタにデプロイすることが可能です。

このように概念を見ていくと、K8sで使われる技術はLinuxコンテナの技術の応用であることにも気づかれるでしょう。

# Kubernetesを運用するうえで出てくる問題点

私のこれまでの経験でも、またベルフェイスでもそうなのですが、K8sをツールとしてインストール/運用したりDockerホストを構築したりすることは行っていません。

AmazonやGoogleの**クラウドサービスで提供されるマネージドサービス**でK8sを利用しています。

マネージドサービスを利用するとK8s自体の複雑な管理/運用は不要 (クラウド任せ) になるので、エンジニアはより開発に注力できることになります。
ただし、運用面で何もしなくて良いわけでは当然なく、私がこの5年間で実感した **K8sのマネージドサービスを使う上での問題点** に絞って2点ご紹介します。

## バージョンアップのつらみ

K8sは仕様こそある程度落ち着いてきましたが、現在も活発に開発が進んでいます。
2021年1月現在の最新は、v1.20.0ですがこれもすぐに過去のバージョンとなってしまうことでしょう。
https://kubernetes.io/docs/setup/release/notes/#downloads-for-v1-20-0

K8sはOSSであり、ベンダーニュートラルなツールです。
ただ、マネージドサービスはベンダーディペンドなので、この間の差はユーザが埋めることになります。

具体的には、クラウド側では**ある時点で新規インストールできるバージョンとサポートするバージョンが決められている**ということになります。
ユーザは定期的にマネージドのK8sクラスタのバージョンを上げる運用が必要になります。
バージョンアップにより、K8sとして仕様が変わり最悪デプロイされたアプリケーションが動作しなくなる可能性もあるので注意が必要です。

慣れてしまえば、ポイントは決まっているのでバージョンアップは恐怖の作業ではないですが、それでもつらみの運用とはいえるでしょう。

## キャパシティプランニングは慎重に

先述の通り、K8sはPodのオートスケーリングが可能ですので、本番運用ではぜひ採用/設定すべきです。

しかし、Pod単体のキャパシティプランニング (利用リソース量調整) まで自動で行ってくれるわけではありません。
たとえば、アプリケーションの動作のために、

- 2vCPU
- 2GBメモリ

という要件のところ、たとえばこれを下回る以下のような設定を行ったとすると記載のような不具合を生じます。

- <font color="red">1vCPU</font>　…　CPU使用率が張り付いてパフォーマンスが相当に悪化
- <font color="red">1GBメモリ</font>　…　コンテナ内部でOOM (Out Of Memory) が引き起こされ、Podが上がってもすぐに落ちる :fearful:

とくに、私はメモリ不足による一大インシデントを経験しています。
**キャパシティプランニングほどITインフラにとって重要なものはないので、<font color="red">十分に余裕を見た設計が肝要</font>**です。

# おわりに

ここ15年くらいの仮想の歴史を繙きながら、コンテナのことをいろいろ書いてきましたが、結局のところシステムにおける運用負荷を完全に0にすることはできません。

ただし、**コンテナを活用することで可能な限りの運用効率化・可視化・負荷低減施策など継続的なプロセス改善を行い、限りなく0に近い運用負荷水準にすることは可能**だと考えています。

ベルフェイスでは、AWSを利用したコンテナ活用を推進しておりまして、今後も安定性の高いシステム基盤作りに取り組んでいきます。

ベルフェイスは，現在積極採用中です:smiley:
一緒にOpslessに取り組みたい人いませんか:interrobang:
https://recruit.bell-face.com/