基础设施设计入门(下篇)
基础设施设计入门(上)的续篇。
在上一篇文章的结尾处,提到了以下的内容。
-
- アプリとサーバーが密結合
- アプリとロードバランサーが密結合
揭示这些问题,并将其与容器、容器编排器以及基础设施即代码(IaC)的话题联系起来。
直接在服务器上安装和运行应用程序的挑战
考虑将应用程序部署在服务器上。例如,考虑使用 AWS 的 EC2 或者租用樱花的 VPS 服务器。
如何在服务器上具体运行应用程序?
在服务器上实现的方法与本地运行的方法相同是现在首先考虑到的方法。
比如说,以 Python 为例。
-
- 然るべき Python のバージョンをサーバーにインストール
-
- 然るべきライブラリを pip install -r requirements.txt などでインストール
python main.py などを実行して動かす
需要最少的工作。
实际上并不仅仅如此。在应用程序中,还需要根据需要修改服务器的设置。
-
- ログファイルにログを書き出し続けるとディスクが逼迫するので、ログローテートを正しくセットする
-
- リクエストを受けられるように、特定のポートを開く。サーバーに侵入されては困るのでそれ以外は閉じる
-
- リクエストを受けて正しく返せるようにルーティングを設定する
- …
这样一来,就能够先让它运动起来了。
现在,这里存在着以下这样的问题。
複雑なセットアップと管理:
とにかくだるい。前の記事で説明した通り、上に示したセットアップが必要なサーバーは1台ではない。これを何度も繰り返すために 重厚な手順書 を必要とする。またステップ数が多いとそれだけ時間もかかるし、間違いも起きやすい。それを間違えず、迅速に黙々と行う専任の インフラ職人 を必要とする。もちろん揶揄している。
環境依存性と互換性の問題:
いわゆる 「ローカルでは動いていたんですけどね」問題 。ローカルの開発環境では動いていたけど、本番環境だと途端に動かなくなる。理由は様々で、例えばローカルの Mac だと動くけど Debian ベースのサーバー環境だとインストールされる依存ライブラリに微妙に差異があって動かないとか、シンプルにデフォルトの文字コードがローカルと違うから、だとか。こういうしょーもない差異は結構ストレス。
バージョン管理とライブラリの依存関係:
一般にサーバーのスペックは高くて、メモリは 64 GB とか普通にある。したがって、 複数のアプリを一台のサーバーに同居させることも常。このとき、例えば2つの異なる Python のアプリケーションを同時に同じサーバーで動かそうとしたときに、一方のアプリはライブラリの version 1.0 を必要とするけど、もう一方のやつは同じライブラリの version 0.9 を必要とする、といったときに、明らかにめんどくさい。2つのアプリが、同じサーバーに動いているというだけで 密結合 になっている。
リソースの衝突と分離:
複数のアプリが同じサーバーで実行される場合、 CPU やメモリ、ディスクスペースなどのリソースの競合が発生する可能性がある。例えば一つのアプリにバグがあって、サーバーのメモリを全部使い果たす自体になれば、それが原因で同居している別のアプリケーションも動かない、という別の問題が発生する。これもアプリ同士が 密結合 になっている例だ。
解决这些问题的方法,一直以来都提供了一些。即所谓的虚拟化技术。
作为执行Java的环境,JVM(Java虚拟机)和GraalVM是著名的虚拟化技术之一。通过将JVM插入应用程序和操作系统之间,可以防止操作系统与应用程序之间的紧密耦合。
Python和Ruby也是一种虚拟化技术,通过逻辑上隔离环境,如venv和rbenv等,旨在实现在库管理等方面实现松耦合的应用。
VirtualBox和VMware是一种技术,它们不仅能够虚拟化应用程序,还可以从操作系统开始完全进行虚拟化。这些被称为虚拟机。有一个名为Vagrant的工具,可以轻松地将虚拟机引入VirtualBox和VMware,其中的虚拟机映像可供任何人使用,就像现在的Docker Hub一样。
如果使用虚拟机技术(VM),可以实现中间件如Nginx和MySQL的虚拟化。也就是说,将Nginx等适当地设置在虚拟机中,并通过重复使用该虚拟机映像来解决上述繁琐的“安装设置”问题。
在这里贪心的问题是,当重复使用虚拟机时,虚拟机镜像的大小非常大。它可能有几个GB到几十GB。每次都要下载和启动它,这会带来太多的额外负担。
如果能像使用 python main.py 命令运行一个应用程序那样简便地启动和执行虚拟机,那就太好了。
这里可以实现你的愿望的是集装箱。
集装箱
如果要用简单的比喻来解释容器,可以说它就像是一个小型的虚拟机。它继承了虚拟机的“隔离环境”的特性,而且体积小,大约只有几百兆到几个千兆字节的大小。
小さな VM というのは比喩に過ぎないことを強調しておく。厳密には、 VM に見えるように OS などを擬似的に模倣しているイメージ。 VM に接続するときにターミナルを開くけれども、コンテナでどうようのことをする場合には 疑似ターミナル を開く。わざわざ “疑似” という言葉を付けたくなるくらいには、やはり模倣に過ぎない。
コンテナ技術として有名なものは Docker であろう。ここでコンテナ技術と呼んでいるのは、コンテナを作るのはもちろん、コンテナを動かす環境も同時に含んでいる。 Docker の他のコンテナ技術と言えば rkt や CRI-O, containerd など。ここでは Docker の話をする。
在使用Docker创建容器时,需要编写类似下面的Dockerfile。以下是一个示例。
# 公式のPython 3.9イメージをベースイメージとして使用する
FROM python:3.9
# コンテナ内の作業ディレクトリを設定する
WORKDIR /app
# requirements.txtファイルを作業ディレクトリにコピーする
COPY requirements.txt .
# requirements.txtから依存関係をインストールする
RUN pip install --no-cache-dir -r requirements.txt
# main.pyファイルを作業ディレクトリにコピーする
COPY main.py .
# エントリーポイントをmain.pyファイルを実行するように設定する
CMD ["python", "main.py"]
Dockerfile 相当于虚拟机的设计说明书或虚拟机构建操作步骤的手册。而且令人高兴的是,这里定义的内容会自动执行。这样一来,“非常痛苦”的问题就能够解决了。
在这里,我们提到一下使用集装箱进行运营。
假设我们考虑对于已经创建的容器镜像进行一些修改的情况。在这种情况下,我们可以通过进入容器并进行配置修改来达到目的。
然而,如果继续这样做,会再次出现因手动进行设置而带来的“非常繁琐”的问题。
ではどうすればよいか。この場合、 コンテナイメージを壊して、 Dockerfile を書き換えて、コンテナイメージを作り直す ということ。このようにすることで、イメージの中身は Dockerfile と同期される。 “Dockerfile にかかれていない特別な手順” というものが存在しなくなる。それ故、上に述べた「とにかくだるい」問題も解消される。
これは「コンテナイメージを一度構築したら不変である(それ故、修正が必要ならば一度壊して作り直す)」という風に言い換えられる。このようなことをコンテナの イミュータビリティ (immutability) と表現することがある。イミュータビリティとは日本語で「不変性」と表現されるもの。コンテナの運用はイミュータビリティを意識する。
このような運用が想定されていることから、コンテナのベースイメージによっては、 cd や cat のような、サーバーの作業に当然必要なコマンドすら入っていないことはよくある。そのような「人間が作業する」ということを徹底的に排除することで、コンテナに詰め込むのに必要なコマンドのバイナリなどを無くして、極限までイメージのサイズを小さくしようとする。
容器编排器
现在,让我们关注一下如何移动集装箱。
コンテナには一つのアプリケーションが詰め込まれているとしよう(高凝集である)。一つのプロダクトを動かすには、アプリケーションがたった一つあれば十分ということはほとんど無いだろう。フロントエンドとバックエンドが別れていることは常であるし、 DB や LoadBalancer といったアプリも必要である。しかも、これらは互いに 協調して動作しなければならない。
協調して動作するとはどういうことか。それは例えば、「LoadBalancer のコンテナがリクエストを受けたら、このアプリのコンテナに流さねばならない」「このアプリのコンテナは DB のコンテナと通信をする必要があるけれど、こっちのアプリのコンテナは DB に接続する必要がない」などということ。
换句话说,需要管理容器之间的关系。
将负责此管理的实体称为容器编排器。
作为一个容器编排工具,第一个让人想到的是 Docker Compose。只需编写一个包含容器之间相互交互的描述文件,然后运行 docker compose up 命令,即可让容器按照描述文件中的要求运行。以下是我自己编写的用于让我的 Web 应用访问 MySQL 的 docker-compose.yaml 文件的示例:
version: '3'
services:
webserver:
image: myimage
ports:
- 8080:8080
depends_on:
- mysql
mysql:
image: mysql
environment:
- MYSQL_ROOT_PASSWORD=secret
对于开发目的来说非常方便。
那么,如果是在实际场景中使用呢?这就存在问题了。
因为Docker Compose只支持在一台服务器中运行多个容器的用例。对于生产环境,正如之前提到的,我们需要在多台服务器上运行多个相同的应用程序以确保容错性和可扩展性。换句话说,Docker Compose无法满足在这方面的生产环境需求。
那么每个服务器都要进去,然后在各自的服务器上执行 docker compose,对吗?这也是一种方法,但是这样一来,就无法享受到上面提到的”只需编写docker-compose.yaml并运行docker-compose up即可一次性运行所有服务”的优点了。
在这里,我们需要的只是一个能够定义容器运行方式,能够在多台服务器上灵活运行的工具,就像 docker-compose.yaml 一样。
有这样的东西吗?有的。那就是 Kubernetes 和 Amazon ECS 等。
Kubernetes和ECS都提供了在多个服务器上运行容器的机制。以前有Docker Swarm和Methos等,但现在这些比较强大。AWS的EKS(弹性Kubernetes服务)、Azure的AKS(Azure Kubernetes服务)和GCP的GKE(Google Kubernetes引擎)都是将Kubernetes封装提供的服务。
Kubernetes 是谷歌源自的开源软件,早期被称为Borg。有关其背景细节在之前介绍过的《没人读过的SRE书》中有详细描述。
简单解释Kubernetes的使用方法。
首先我们要写一个被称为清单文件的东西。它替代了docker-compose.yaml,描述了容器应该如何运行。
apiVersion: apps/v1
kind: Deployment
metadata:
name: mywebapp-deployment
spec:
replicas: 2
selector:
matchLabels:
app: mywebapp
template:
metadata:
labels:
app: mywebapp
spec:
containers:
- name: mywebapp
image: mywebapp:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: mywebapp-service
spec:
type: LoadBalancer
selector:
app: mywebapp
ports:
- protocol: TCP
port: 80
targetPort: 80
只需创建此文件并运行 “kubectl apply” 命令,就能惊人地构建先前文章中介绍的以下配置。

顺便提一下,ECS和EKS(AWS提供的Kubernetes服务)之间的区别。
通过书写类似上述定义文件,ECS也能够实现按照所写的要求构建基础架构,与Kubernetes在这方面相同。然而,ECS采用了与Kubernetes不同的技术来实现这一点。在利用方面,最大的区别在于谁来进行服务器管理。
使用ECS时,用户无需关心服务器管理问题,只需意识到他们希望以怎样的配置来运行容器即可。
一方,EKS 则不同。必须要管理 Kubernetes 集群本身。例如,Kubernetes 大约每四个月需要更新一次集群。这项工作相当繁琐。
为什么要使用EKS到这个程度?原因在于Kubernetes的可定制性。通过使用自定义资源和操作者模式,可以轻松地创建自己的kubectl命令,并且可以相当程度地定制行为。如果在一个规模较大的公司中,有多个产品都使用Kubernetes进行运行的决策,那么人们也会希望进行这样的定制。
順便提一下,在本地能运行 Kubernetes 吗?可以。例如 Docker Desktop 上有 Kubernetes 模式。它可以接受 Kubernetes 的配置文件并在本地进行复现。
在企业内部管理Kubernetes需要相当大的体力。请考虑一下。如果有Kubernetes,应用程序会变得轻松,但构建Kubernetes本身需要直接在服务器上进行操作。虽然有很多工具可以方便地进行这方面的任务,但是预料到会遇到很多困难。实际上,像LINE和Yahoo这样的公司都有专门负责管理Kubernetes的团队。考虑到这个管理工作,我们也可以选择将其交给云服务提供商,作为对方提供服务的代价,付钱给他们。
基於宣言的架設和IaC
这不是关于基础设施的讨论,有一个叫做声明式配置的词。还有一个类似动机的词叫做声明式编程。
これらは設定やプログラミングの記法の一種で、「どのように 理想的な状態にするか」という手続きに注目するのではなく、「最終的に実現したい状態は なにか」に注目した書き方。
例えば最適化理論の定式化などは、いわゆる宣言的設定のニュアンスが強い。
\begin{align}
\min_{\vec{x}} \ & f(\vec{x}) \\
\text{s.t.} \ & g_1(\vec{x}) \leq 0, \\
& g_2(\vec{x}) \leq 0, \\
& \vdots \\
& g_M(\vec{x})\leq 0 .
\end{align}
$\text{s.t.}$ のところに制約条件が表現されているわけだけども、ここには「どのようにこの制約条件を満たすか」については言及していない。最適化のソルバーなどは、この目的関数と制約条件を上のような雰囲気で設定するだけでよい、といったインターフェースを準備してくれてたりする。
可以將這個陳述翻譯為:宣言式設定的反義詞是命令式設定。命令式設定用於描述「如何實現」的方式。大部分的編程語言屬於這一類型。
为什么提到声明性设置?因为上面的清单文件正是指的那个。
サーバー管理を手動でやる場合、 手順書 といったものが必要。ステップごとにやることが記載されていて、それを順番どおりにやっていけば達成できる、という「どのように達成するか」にフォーカスした存在。
この命令的設定だと、結構煩雑になる。例えば「冗長化のために、アプリが4つ動いていなければならない」という理想的な状態があるとする。それを実現するには、 今アプリがいくつ動いているか を知って、それに応じて オペレーションを変更しなければならない。例えば今アプリが一つも動いていないとしよう。その場合は 4 つのアプリを起動する必要がある。あるいはもともと 6 つのアプリが動いていて、それのスケールダウンを考えているとしよう。その場合は 2 つのアプリを停止させる必要がある。といった具合。
ここでマニフェストファイルを見る。ここでは
...
spec:
replicas: 2
...
というふうに、「とにかく 2 つ動いてほしい」と宣言しているだけ。 Kubernetes や ECS は賢くて、 どんなことが起きてもアプリが 2 つ動いている状態を実現してくれる。 Kubernetes は今コンテナがいくつ動いているかを常に監視していて、もしも障害などでアプリが 1 つしか動いていない状態が数秒あったら、 自動的に 2 つ目のコンテナをどこかのサーバーで動かしてくれる。この replicas の値を例えば 5 にして、 kubectl apply をしたら、自動的に 3 つのコンテナをどこかのサーバーで起動してくれる。
これは耐障害性があることを示している。たとえばサーバーが一つ落ちた場合、その上で動いているコンテナがごっそり消える。 Kubernetes は勝手にそれを検知して、どこか空いてるサーバーを自分で探して、ごっそり消えたコンテナを全部起動しなおしてくれる。
この仕組みを応用して面白い提供の仕方をしているのが、 Google の GCP。 Kubernetes のサーバーを選択するときに プリエンプティブル VM というものを選ぶことができる。これは、24時間くらいで自動的にシャットダウンしてしまうサーバー。その分安い。そんなの困る!と思うかもしれないが、 Kubernetes のこの性質を使えばそれがさほど問題にならないのも理解できると思う。
ここから IaC (Infrastructure as Code) という言葉を導入したい。これは宣言的設定と密接に関わりがある。
IaC とは、インフラの状態を コードとして管理する インフラ管理のアプローチの一つ。コードというか、上のマニフェストファイルのような テキストファイル としてインフラを設定する、ということ。
これができると何がいいかというと、 Git のようなバージョン管理の仕組みをインフラにも持ち込めること。 GitHub で PR ベースでレビューもできたりと、アプリのコードの開発フローと同じやり方が、インフラにおいてもできる。
可以感受到IaC(基础设施即代码)和声明性配置的良好契合度。用文字来表达”理想基础设施状态”,并对其进行版本控制。
在代表性的IaC工具中,包括Terraform、Ansible、Chef、Puppet等。
结束
虽然标榜为基础设施入门,但力不从心。我只想简单提及这个相关领域的关键词。
-
- インフラのアーキテクチャ
インフラのアーキテクチャにはいくつか重要なアーキテクチャパターンがある。 モノリシックアーキテクチャ、 マイクロサービスアーキテクチャ、 イベント駆動アーキテクチャ など。
DB の話
DBはでっかい話題がある。 RDB もそうだけど NoSQL みたいなものも含めて。
ネットワークの話
サーキットブレーカーや Istio のようなサービスメッシュの考え方
DevOps, DevSecOps, MLOps
CI/CD とか。 Chaos Engineering とか。
结束