コンテナを使ったシステムのアーキテクチャパターンが掲載されている、分散システムデザインパターンという良書に出会ったので、Kubernetesの勉強を兼ねて各デザインパターンを実装してみましたので、ご紹介です。
この本は執筆者の論文が元になっており、そちらの翻訳記事もあるので、一読することをおすすめします。
Qiita | コンテナ・デザイン・パターンの論文要約
本記事で扱うデザインパターンとその例
分散システムデザインパターン中では、大別して2種のデザインパターンが紹介されています。
- 1台のノード上で複数のコンテナが動作するシングルノードパターン
- 複数のノード上で実現されるマルチノードパターン
本記事では、シングルノードパターンから以下のデザインパターンとその例を、Kubernetesでの実装例とともに解説します。
- サイドカーパターン
- ex1. git-sync
- ex2. topz
- アンバサダパターン
- ex1. twemproxy
- アダプタパターン
- ex1. redis exporter
- ex2. mysql-healthcheck
リポジトリ
サイドカーパターン
サイドカーパターンはポッド(ノード)の中で、主となるコンテナを補助する様なコンテナを持つ構成です。
ex1. git-sync
git-syncというツールを利用したサイドカーパターンの例です。
git-syncは定期的にリポジトリへアクセスし、常に最新のコードにローカルのコードを保つツールです。
この例での構成は下記のようになります。
+--------+ +--------+
| client | | GitHub | html file
+--------+ +--------+
| |
| access | git pull
| |
+---------------------------------------+
| | | |
| +-------------+ +-------------+ |
| | Nginx | | git-sync | |
| | <Container> | | <Container> | |
| +-------------+ +-------------+ |
| | | |
| | mount | mount |
| | | |
| | +-------------+ | |
| +--| source code |--+ |
| | <Voluem> | |
| +-------------+ |
| <Pod> |
+---------------------------------------+
- git-syncが定期的にgitリポジトリへアクセスし、最新のコードを取得します。
- 取得されたコードはノード上のVolumeへ保存され、git-syncコンテナとメインとなるコンテナ(ここではNginx)からアクセスできるように構築されています。
- この構成により、Nginxのコンテナに手を入れなくとも、常に最新のコードをNginxのコンテナから参照できるようになります。
※コードの更新後、再起動が必要なケースでは、git-syncにもう少し複雑な設定を追加する必要があります。
マニフェスト
マニフェストファイルの実装は下記のようになります。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: git-sync
spec:
replicas: 1
selector:
matchLabels:
app: git-sync
template:
metadata:
labels:
app: git-sync
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: git-sync-volume
mountPath: /usr/share/nginx
- name: git-sync
image: gcr.io/google_containers/git-sync:v3.1.1
volumeMounts:
- name: git-sync-volume
mountPath: /sync
env:
- name: GIT_SYNC_REPO
value: https://github.com/reireias/git-sync-example.git
- name: GIT_SYNC_BRANCH
value: master
- name: GIT_SYNC_ROOT
value: /sync
- name: GIT_SYNC_DEST
value: html
volumes:
- name: git-sync-volume
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: git-sync
labels:
app: git-sync
spec:
selector:
app: git-sync
type: NodePort
ports:
- port: 80
targetPort: 80
解説
- git-syncコンテナとNginxコンテナを持つPodを1台構築するDeploymentが定義されています
- Deployment中のPodの設定では、コードをコンテナ間で共有するためのvolumeを定義しています
- git-syncで最新のコードを取得するリポジトリは、git-syncコンテナの環境変数で指定しています
- もし、自分で起動して確認する場合は、自分でpush可能なリポジトリを指定するために、上記リポジトリをforkし、そのリポジトリを利用してください
- Nginxのコンテナを外部へ公開するための、Serviceを定義しています
minikube上での実行
miniubeでの実行例です。
# デプロイする
kubectl apply -f git-sync.yml
# ブラウザでgit-syncサービス(Nginx)へアクセスする
minikube service git-sync
# git-syncで参照しているリポジトリのコードを更新する
# サイドブラウザでアクセスし、htmlファイル等が更新されていることを確認する
ex2. topz
サイドカーパターンの2つめの例はtopzを用いたリソース監視です。
構成は下記のようになります。
+---------------------------------------------+
| +---------------------------------------+ |
| | +-------------+ +-------------+ | |
| | | Nginx | | topz | | |
| | | <Container> | | <Container> | | |
| | +-------------+ +-------------+ | |
| | <PID namespace> | |
| +---------------------------------------+ |
| <Pod> |
+---------------------------------------------+
- topzコンテナとNginxコンテナ間でPID namespaceを共有させています
- topzからNginxのプロセス情報が参照できるようになっています
- これをwebアクセスで参照できるのがtopzの機能です
マニフェスト
マニフェストファイルの実装は下記のようになります。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: topz
spec:
replicas: 1
selector:
matchLabels:
app: topz
template:
metadata:
labels:
app: topz
spec:
shareProcessNamespace: true
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
- name: topz
image: brendanburns/topz:db0fa58
ports:
- containerPort: 8080
command:
- /server
args:
- -addr
- 0.0.0.0:8080
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
app: topz
type: NodePort
ports:
- port: 80
targetPort: 80
name: nginx
---
apiVersion: v1
kind: Service
metadata:
name: topz
labels:
app: topz
spec:
selector:
app: topz
type: NodePort
ports:
- port: 8080
targetPort: 8080
name: topz
解説
- DeploymentでNginxとtopzコンテナを持つPodを定義しています
- 外部からアクセスできるように、topzとNginxのそれぞれのServeceを定義しています
minikube上での実行
# デプロイします
kubectl apply -f topz.yml
# サービス一覧からエンドポイントを確認できます
minikube service list
|-------------|----------------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|----------------------|-----------------------------|
| default | nginx | http://192.168.39.196:31831 |
| default | topz | http://192.168.39.196:30532 |
|-------------|----------------------|-----------------------------|
# 下記コマンドでNginxサービスへアクセスできます
minikube service nginx
# topz側のエンドポイントは下記コマンドで表示できるので、ブラウザでアクセスします
echo "$(minikube service topz --url)/topz"
# リソース使用状況が表示されるはずです
アンバサダパターン
アンバサダパターンはメインのコンテナから外部へ接続する際に仲介する仕組みです。
キャッシュを利用したり、リクエストに応じて接続先を切り替えたりする用途で利用します。
ex1. twemproxy
Redis用のproxyサーバーであるtwemproxyを利用する例です。
構成は下記のようになっています。
+-----------------+
| +-------------+ |
| | Nginx | |
| | <Container> | |
| +-------------+ |
| | |
| +-------------+ |
| | twemproxy | |
| | <Container> | |
| +-------------+ |
| | <Pod> |
+-----------------+
|
+-----------------+-----------------+
| | |
+---------+-----------------+-----------------+---------+
| | | | |
| +---------------+ +---------------+ +---------------+ |
| | Redis Shard 0 | | Redis Shard 1 | | Redis Shard 2 | |
| | <Pod> | | <Pod> | | <Pod> | |
| +---------------+ +---------------+ +---------------+ |
| <StatefulSet> |
+-------------------------------------------------------+
- twemproxyは高速かつ軽量なmemcached/Redis用プロキシです
- リクエスト中のキーに応じて接続先のRedisサーバーを自動的に選択してくれます
- ハッシュを利用しているので、同じキーの保存/取得リクエストはすべて同じRedisサーバーへ送られます
- この仕組みにより、Redisサーバーの負荷の分散が実現できます。
マニフェスト
マニュフェストは下記のようになります。
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sharded-redis
spec:
selector:
matchLabels:
app: redis
serviceName: "redis"
replicas: 3
template:
metadata:
labels:
app: redis
spec:
terminationGracePeriodSeconds: 10
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
name: redis
---
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
ports:
- port: 6379
name: redis
clusterIP: None
selector:
app: redis
---
apiVersion: v1
kind: Pod
metadata:
name: ambassador-example
spec:
containers:
- name: nginx
image: nginx
- name: twemproxy
image: ganomede/twemproxy
command:
- "nutcracker"
- "-c"
- "/etc/config/nutcracker.yml"
- "-v"
- "7"
- "-s"
- "6222"
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: twem-config
解説
- StatefulSetにて、Redisコンテナを3台定義しています
- DeploymentにてNginxとtwemproxyコンテナをもつPodを定義しています
- 下記に記載してあるtwemproxy用の設定ファイルを参照できるようにconfigMapとそのマウントを定義しています
- nginxコンテナからは同一Pod上にtwemproxyコンテナが存在するので、
127.0.0.1:6379
でtwemproxyに接続ができます
事前にconfigMapに登録しておく、twemproxyの設定ファイルは下記のようになります。
redis:
listen: 127.0.0.1:6379
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
timeout: 400
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- sharded-redis-0.redis:6379:1
- sharded-redis-1.redis:6379:1
- sharded-redis-2.redis:6379:1
minikube上での実行
# 最初にconfigMapにtwemproxy用の設定ファイルを登録します
kubectl create configmap twem-config --from-file=./nutcracker.yml
# デプロイします
kubectl apply -f twemproxy.yml
# redisクライアントでの動作確認
# nginxコンテナでbashを起動します
kubectl exec -it ambassador-example --container nginx bash
# redis-cliをインストールします
apt update
apt install -y redis-tools
# twemproxy経由で値をRedisに格納します
redis-cli
127.0.0.1:6379> set hoge 1
127.0.0.1:6379> set fuga 2
# twemproxy経由で値を取得します
127.0.0.1:6379> get hoge
# ローカルマシンに戻り、下記の各コマンドで、各Redisの登録されたキーを確認できます
kubectl exec -it sharded-redis-0 --container redis redis-cli
127.0.0.1:6379> keys *
kubectl exec -it sharded-redis-1 --container redis redis-cli
127.0.0.1:6379> keys *
kubectl exec -it sharded-redis-2 --container redis redis-cli
127.0.0.1:6379> keys *
アダプタパターン
アダプタパターンはメインのコンテナの不均一なインターフェースを吸収し、統一されたインターフェースを外部に公開するのに利用するパターンです。
監視用のインターフェースを提供するのによく利用します。
ex1. redis-exporter
redis_exporterを使い、監視ツールPrometheus用のインターフェースを提供するパターンです。
構成は下記のようになります。
+------------+ +-------+
| Prometheus | | app |
+------------+ +-------+
| get metrics |
+---------+----------------------------+
| | | |
| +----------------+ +-------------+ |
| | redis_exporter |---| redis | |
| | <Container> | | <Container> | |
| +----------------+ +-------------+ |
| <Pod> |
+--------------------------------------+
- RedisはPrometheus用リソース監視I/Fは持っていませんが、この構成で提供できます
マニフェスト
マニュフェストは以下のようになります。
---
apiVersion: v1
kind: Pod
metadata:
name: adapter-example
namespace: default
labels:
app: exporter
spec:
containers:
- name: redis
image: redis
- name: exporter
image: oliver006/redis_exporter
ports:
- containerPort: 9121
---
apiVersion: v1
kind: Service
metadata:
name: exporter
labels:
app: exporter
spec:
selector:
app: exporter
type: NodePort
ports:
- port: 9121
targetPort: 9121
解説
- Pod内にRedisコンテナとredis_exporterコンテナを定義しています
- Serviceでredis_exporterを外部に公開しています
minikube上での実行
# デプロイします
kubectl apply -f redis-with-exporter.yml
# 下記コマンドで、Prometheus用I/Fにアクセスし、メトリクスが取得できることを確認します
curl $(minikube service exporter --url)/metrics
ex2. mysql rich healthcheck
golang製のリッチなMySQL用ヘルスチェックツールによるアダプタパターンです。
構成は下記のようになります。
+--------+
| Client |
+--------+
|
| access to "/"
|
+---------------------------------------------+
| | |
| +-------------+ +-------------------+ |
| | MySQL | query | mysql-healthcheck | |
| | <Container> |-------| <Container> | |
| +-------------+ +-------------------+ |
| <Pod> |
+---------------------------------------------+
- reireias/mysql-healthcheckを利用して、MySQLに特定のクエリを発行してヘルスチェックを行います
- mysql-healthcheckコンテナへアクセスがあった際にクエリは発行されます
マニフェスト
マニュフェストは下記のようになります。
---
apiVersion: v1
kind: Service
metadata:
name: healthcheck
spec:
type: NodePort
ports:
- name: healthcheck
port: 8080
targetPort: 8080
selector:
app: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.6
env:
- name: MYSQL_ROOT_PASSWORD
value: password
- name: MYSQL_USER
value: user
- name: MYSQL_PASSWORD
value: password
- name: MYSQL_DATABASE
value: test
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: healthcheck
image: reireias/mysql-healthcheck
env:
- name: MYSQL_USER
value: user
- name: MYSQL_PASSWORD
value: password
- name: MYSQL_DATABASE
value: test
- name: MYSQL_QUERY
value: 'show databases;'
- name: MYSQL_HOST
value: mysql
- name: MYSQL_PORT
value: '3306'
- name: ADDRESS
value: 0.0.0.0:8080
ports:
- name: healthcheck
containerPort: 8080
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
---
kind: PersistentVolume
apiVersion: v1
metadata:
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
解説
- MySLQコンテナとmysql-healthcheckコンテナをDeploymentで定義しています
- 個々のコンテナの環境変数で設定を指定しています
- mysql-healthcheckをServiceで公開するように定義しています
- PersistentVolumeは依存関係で事前に作成するため、別のマニュフェストファイルとして作成しています
minikube上での実行
# PersistentVolumeをデプロイします
kubectl apply -f mysql-pv.yml
# PodとServiceをデプロイします
kubectl apply -f mysql-with-ritch-healthcheck.yml
# ヘルスチェックが実行されるか確認します
curl $(minikube service healthcheck --url)
# OKと返るはずです
まとめ
分散システムで利用できるデザインパターンのうち、シングルノードパターンを実例とともにいくつか紹介しました。
意外と既に実践していたパターンがいくつかあったのではないでしょうか?
デザインパターンは知っておくと便利ですし、それに適したコンテナがDockerHubで公開されていたりするので、活用していきましょう。