不変のインフラストラクチャとは何か、なぜ重要なのか

本記事では、**Alibaba Cloud**上に不変のインフラを複数のレイヤーで構築する技術的な実現可能性を紹介し、議論しています。

#序章
従来のクラスタ環境では、エンジニアや管理者はサーバにランダムに ssh 接続してパッケージをインストールしたり、サーバごとに設定を微調整したりしていました。アプリケーションは手動で展開されます。依存関係は偶発的に導入されます。設定は変更され、予測できないほどにドリフトします。人的要因が遅かれ早かれ大惨事になるのは明らかです。

開発、デプロイ、運用の間に不必要に導入される矛盾を減らすために、不変のインフラストラクチャは一連の分離されたスナップショットを構築する傾向がありますが、これは修正されません。そのため、何か問題があればいつでも以前の既知の状態にロールバックすることができます。

クラウドベースのエコシステムでは、整合性⇒移植性⇒自動化⇒拡張性というようにまとめることができるかもしれません。この連鎖の礎となる一貫性を実現するためには、不変のインフラストラクチャが最適なモデルかもしれません。

具体的な技術というよりはパラダイムとして、不変性インフラストラクチャは様々なアプローチと実装が可能です。この記事では、アプリケーションプラットフォーム、ファイルシステム、パッケージ管理、プロビジョナーの4つのレイヤーに分類します。

#不変アプリケーションプラットフォーム
ここでいうアプリケーションプラットフォームとは、JavaやPythonなどの特定のランタイムを指すこともあれば、コンテナ技術のような汎用的なものを指すこともあります。このセクションでは、Dockerの人気と関連性を考慮して、Dockerについてのみ説明します。

不変性はコンテナ技術の大原則であり、一度構築されたDockerイメージは決して変更されません。本当に必要な変更は新しいイメージに組み込まれ、別のイメージレイヤーにカプセル化される必要があります。そのため、すべてのアプリケーション環境を強制的に同じにし、移植性を持たせることができます。

コンテナサービスはAlibaba Cloud上ですぐに使えるようになっているので、自分で設定する必要がありません。ホスティングノードとVPCをオンにした後、データストアのmongoDBやMySQL、nginxやphpなどのウェブプラットフォームなど、一般的によく使われているアプリケーションプラットフォームの束がすでにアプリケーションを実行するために用意されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/f8d609ce-274d-2270-48a4-dbdea859d048.png)

また、Dockerイメージのメタデータを記録したDockerfileを編集することで、いつでも自分の環境をカスタマイズすることができます。

#不変ファイルシステム
Dockerや他のコンテナ技術はアプリケーションのランタイムの不変性を提供しますが、すべてのシナリオをカバーすることはできません。Dockerはファイルシステム層でOverlayFSやUnionFSドライバを使用しますが、これらは永続性がなく、ステートレスで揮発性のあるアプリケーションにしか適していません。これはマイクロサービスベースのアーキテクチャのベストプラクティスの一つと考えられていますが、すべてのアプリケーション、一般的にはレガシーなアプリケーションに最適であるとは考えられません。このセクションでは、Linux ユーザ空間で抽象化された階層として動作する OSTree を紹介します。

OSTree のルートディレクトリの下では、書き込み可能なのは `/etc` と `/var` だけで、通常は設定やアプリケーションディレクトリにマップされます。今回は、Alibaba Cloud Elastic Compute Service上にCentOS 7.3のインスタンスで実験環境を設定してみますので、簡単な例をステップバイステップで見ていきます。

この例では、`/var/demo/demo.py` の下で `/etc/demo/demo.conf` から設定を読み込んだアプリケーションが動作していると仮定します。設定の切り替えが本当に有効で、アプリケーション自体に依存しないかどうかをテストしたいと思います。

簡単にするために、この例はできるだけ抽象的なものにします。`/var/demo/demo.py`は`/etc/demo/demo.conf`から環境名とポートを読み込み、そのポートで動作するhttpサーバを起動して環境名を表示します。

```
/etc/demo/demo.conf
```

```
    [main]
    env = dev
    port = 8000
```

```
/var/demo/demo.py:
```

```
    #!/usr/bin/env python

    import SimpleHTTPServer
    import SocketServer
    import ConfigParser

    config = ConfigParser.RawConfigParser()
    config.read('/etc/demo/demo.conf')

    env = config.get('main', 'env')
    port = config.getint('main', 'port')

    Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

    httpd = SocketServer.TCPServer(("", port), Handler)

    print("serving %s at port %d" % (env, port))
    httpd.serve_forever()
```

まず、必要なパッケージをインストールします。

```
    $ yum install -y ostree
```

そして、新しい ostree リポジトリを初期化します。

```
    $ mkdir -p /etc/demo && cd /etc/demo
    $ ostree --repo=.demo init
```

.demoで始まる.demoを使うのは、ostreeの内部ディレクトリを非表示にしたいからです。

最初のバージョンの demo.conf を新しいブランチ dev にコミットします。

```
    $ ostree --repo=.demo commit --branch=dev ./
    8d7cefc677593c16ecd9eada965fb1ac53d6ae96a6af9fef49a22d164a06e6e2
```
既存のコミットの一覧

```
    $ ostree --repo=.demo refs
    dev
```

ブランチ dev 内のリストディレクトリ

```
   $ ostree --repo=.demo ls dev
    d00755 0 0      0 /
    -00644 0 0     29 /demo.conf
    d00755 0 0      0 /.rdemo
```

ブランチ dev 内の最初のバージョンの demo.conf の内容を表示し、適切にコミットされていることを確認します。

```
    $ ostree --repo=.demo cat dev /demo.conf
    [main]
    env = dev
    port = 8000
```

`var/demo/demo.py` のテスト

```
    chmod +x /var/demo/demo.py
    /var/demo/demo.py
    serving dev at port 8000
```

demo.conf を修正して uat ブランチにコミットします。

```
    $ cat /etc/demo/demo.conf
    [main]
    env = uat
    port = 80
```

作業ディレクトリをブランチにコミットします。

```
    $ ostree --repo=.demo commit --branch=uat ./
    3aca353878a754a887a0308ff5ca6f8ad86057a2175e3f0b194ff51c5e471116
```

再度 `/var/demo/demo.py` を実行します。

```
    /var/demo/demo.py
    serving uat at port 80
```

これで変更が有効になるのがわかりました。コミットされていれば、いつでもファイルの内容を確認することができます。

ブランチdevに`demo.conf`を表示します。

```
    $ ostree --repo=.demo cat dev /demo.conf
    [main]
    env = dev
    port = 8000
```

ブランチ uat で `demo.conf` を表示します。

```
    $ ostree --repo=.demo cat uat /demo.conf
    [main]
    env = uat
    port = 80
```

OSTree は、基本的に、ある意図的に定義された状態から別の状態へのアトミックな移行力を、1つのシンプルなコマンドで提供します。このパワーにより、オンラインアップグレード中のリスクを排除し、誤って変更されることのないように、すべてのデータを保護します。

下の図は、OSTREE がどのようにファイルシステムを回転させて、配備された設定やアプリケーションを新旧の間で切り替えるかを示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/d1249e56-130e-95fc-4eb8-730a32f751fa.png)


Gitと同様に、OSTreeはレポの設定を指定したディレクトリの下に保存します。アップグレード時には、OSTree は基本的な 3 者間差分を実行し、古いものはそのままにして、ローカルでの変更を新しいコピーに適用します。あらゆる種類のファイルシステムと互換性がありますが、OSTreeはBTRFSのようなカーテンの上にインストールすれば、いくつかのネイティブ機能を利用することができます。

OSTree の構造に興味がある方は、[https://ostree.readthedocs.io/en/latest/manual/repo/](https://ostree.readthedocs.io/en/latest/manual/repo/?spm=a2c65.11461447.0.0.1a5217fblcce1a) をチェックアウトすると、より具体的な詳細を知ることができます。

#パッケージ管理
OSTree は完全なファイルシステムツリーのアトミックなアップグレードを可能にし、そのサブシステムである OSTree-RPM は主により高いレベルでのパッケージ管理機能を提供します。あなたがシステム管理者であれば、オペレーティングシステムのパッチ適用には苦労します。OpenSSLを例にとると、最新のパッチを適用しないと、あなたのインフラは、 heartbleedバグのようなセキュリティ上の問題にさらされる可能性があります。最新のパッチを適用すると、互換性の問題が広範囲に発生する可能性があります。rpm-ostreeでは、ここでパッケージシステムを戻すのは非常に苦痛ではないので、安心してアップグレードしてテストすることができるかもしれません。

テスト用に以前の ECS インスタンスを再利用します。

rpm-ostree はパッケージシステムへの変更をデプロイメントとして表示します。すべてのデプロイメントを表示するには

```
   $ rpm-ostree status
```


`yum update`の新規デプロイを実行する

```
    $ rpm-ostree upgrade
```

これを有効にするには、OS を再起動するように指示されます。`rpm-ostree` はローカルのパッケージシステムを引き継いで常にリセット可能な状態にすることを目的としているので、すべての変更を行うには、init システムとパッケージ管理システムを再ロードするために `systemctl reboot` を実行する必要があります。

```
     $ systemctl reboot
```

続いて、opensslのインストールテストを行います。その前に、現在のopensslのバージョンを表示します。

```
    $ openssl version
    OpenSSL 1.0.1e-fips 11 Feb 2013

    $ rpm-ostree install openssl
    Checking out tree 67d659b... done
    Inactive requests:
    openssl (already provided by openssl-1:1.0.2k-12.el7.x86_64)
    Enabled rpm-md repositories: base updates extras
    Updating metadata for 'base': [=============] 100%
    rpm-md repo 'base'; generated: 2018-11-25 16:00:34
    Updating metadata for 'updates': [=============] 100%
    rpm-md repo 'updates'; generated: 2019-01-24 13:56:44
    Updating metadata for 'extras': [=============] 100%
    rpm-md repo 'extras'; generated: 2018-12-10 16:00:03
    Importing metadata [=============] 100%
    Resolving dependencies... done
    Checking out packages (2/2) [=============] 100%
    Running pre scripts... 0 done
    Running post scripts... 1 done
    Writing rpmdb... done
    Writing OSTree commit... done
    Copying /etc changes: 22 modified, 8 removed, 41 added
    Transaction complete; bootconfig swap: no; deployment count change: 0
    Freed: 39.0 MB (pkgcache branches: 2)
    Run "systemctl reboot" to start a reboot

    $ openssl version
    OpenSSL 1.0.2k-fips  26 Jan 2017
```

前の状態にロールバックし、wget が削除されたかどうかをテストします。

```
    $ rpm-ostree rollback
    Moving '67d659bc257b7d47f638f9d7d2146401b85eec7c7eef0122196d72c70553ae66.0' to be first deployment
    Transaction complete; bootconfig swap: no; deployment count change: 0
    Removed:
    openssl-1.0.2k-12.el7.x86_64
    Run "systemctl reboot" to start a reboot

    $ systemctl reboot

    $ openssl version
    OpenSSL 1.0.1e-fips 11 Feb 2013
```

#不変のプロビジョナー
一般的にプロビジョナーとは、ハイパーバイザープール内のホスト、OpenStackではコンピュートノード、Kubernetesではワーカーノードを指す場合があります。これらは、インフラストラクチャの下のインフラストラクチャ、ホストのホスト、またはマシンを構築するマシンのようなものです。

理想的には、このプロビジョナーはプラットフォームとして不変のインフラを提供し、その間に不変であることができる。ここでは、Fedora/CentOS/RHELプラットフォームで一般的に利用されているAtomic Hostを参照することができ、計算リソースがec2インスタンス、ハイパーバイザー、ベアメタルマシンのいずれの種類であっても利用可能です。Atomic Host はまた、システムサービスを runC でコンテナ化したり、ファイルやパッケージシステムのバージョン管理に ostree を使用したり、コンテナホストに分離とセキュリティを提供する異なるカーネルの名前空間を有効にしたりすることで、それ自身を不変のものにしています。

システムサービスは通常、明らかにコンテナ技術の原則に従うホストに直接アクセスする必要があるという疑問があるかもしれません。だからどのようにそれらをコンテナ化することができます。それは、アトミックホストがホストシステムにアクセスしたり変更したりするための特別な権限を持つスーパー特権コンテナを導入しているからです。そして、これらのシステムコンテナは、システムサービスの代わりにインストールステップでsystemdによって管理されるように設定されており、システムが起動したときに自動的に起動することができます。

基本的なsyslogプロトコルをより強力なフィルタリング機能と柔軟な設定で拡張したrsyslogの例を見てみましょう。

```
    $ atomic install registry.access.redhat.com/rhel7/rsyslog
    Pulling registry.access.redhat.com/rhel7/rsyslog:latest ...
    …
    Creating directory at /host//etc/pki/rsyslog
    Installing file at /host//etc/rsyslog.conf
    Installing file at /host//etc/sysconfig/rsyslog
    Installing file at /host//etc/logrotate.d/syslog

    $ atomic run registry.access.redhat.com/rhel7/rsyslog
    docker run -d --privileged --name rsyslog --net=host --pid=host -v
    /etc/pki/rsyslog:/etc/pki/rsyslog ...
```

Alibaba ECSでAtomic Hostを使用するには、[CentOS Atomic Host](https://wiki.centos.org/SpecialInterestGroup/Atomic/Download/?spm=a2c65.11461447.0.0.1a5217fblcce1a)または[Fedora Atomic Host](https://dl.fedoraproject.org/pub/alt/atomic/stable/?spm=a2c65.11461447.0.0.1a5217fblcce1a)でqcow2版のimageをダウンロードし、[カスタムイメージのインポート](https://www.alibabacloud.com/help/ja/doc-detail/25464.htm)に従ってオンプレミスのイメージファイルをECS環境にインポートしてECSインスタンスを作成したり、システムディスクを変更したりすることができます。

#概要
今回の記事では、Docker、ostree、ostree-rpm、Atomic Hostを利用して、アプリケーションプラットフォームやファイルシステムからパッケージ管理、プロビジョナーまで、不変のインフラ層をAlibaba Cloud上に構築し、すべてを一貫性のあるものにすることで、システムの複雑さや人的要因のリスクを下げることができることを簡単にご紹介しました。

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*