はじめに
KubernetesのマニュアルにはTutorialとしていくつか例が載っています。今回はその中で以下の例を実際にやってみたいと思います。
例: Redisを使用したPHPのゲストブックアプリケーションのデプロイ
例: PHP / Redisを使用したゲストブックの例にロギングとメトリクスを追加する
ゲストブックアプリケーションのアーキテクチャー
このチュートリアルでは、KubernetesとDockerを使用した、シンプルなマルチティアのウェブアプリケーションのビルドとデプロイの方法を紹介します。この例は、以下のコンポーネントから構成されています。
- ゲストブックのエントリーを保存するための、シングルインスタンスのRedisマスター
- 読み込みデータ配信用の、複数のレプリケーションされたRedisインスタンス
- 複数のウェブフロントエンドのインスタンス
これから作るゲストブックアプリケーションのアーキテクチャーは上記のように書かれているのですが、図にすると以下のようになります。(私の理解です)
- redis master
- redis slave
- frontend
の順に作っていきます。
Redisマスターのデプロイ
Pod
ゲストブックアプリケーションでは、データを保存するためにRedisを使用します。ゲストブックはRedisのマスターインスタンスにデータを書き込みます。
以下のマニフェストをapplyします。Replica数1のPodをDeploymentでデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-master
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
role: master
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: k8s.gcr.io/redis:e2e
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
$ kubectl apply -f redis-master-deployment.yaml
deployment.apps/redis-master created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-master-6b54579d85-l8hsn 1/1 Running 0 80s
logを確認すると、redisのロゴがアスキーアートで出てますね。
$ kubectl logs redis-master-6b54579d85-l8hsn
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.8.19 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in stand alone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 1
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[1] 23 Aug 12:56:53.934 # Server started, Redis version 2.8.19
[1] 23 Aug 12:56:53.935 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
[1] 23 Aug 12:56:53.935 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
[1] 23 Aug 12:56:53.935 * The server is now ready to accept connections on port 6379
Service
Redisマスターへの通信を制御するためにServiceが必要になります。ここではクラスタ内部のみの通信になりますので、ClusterIPをデプロイします。
apiVersion: v1
kind: Service
metadata:
name: redis-master
labels:
app: redis
role: master
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
role: master
tier: backend
$ kubectl apply -f redis-master-service.yaml
service/redis-master created
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d
redis-master ClusterIP 10.101.227.29 <none> 6379/TCP 10s
Redisスレーブのデプロイ
Pod
ゲストブックアプリケーションでは、スレーブインスタンスからデータを読み込みます。Redisを理解していないのですが、マスターからスレーブにデータはレプリケーションされてるんでしょうね。きっと。
Replica数2のPodをDeploymentでデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-slave
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
role: slave
tier: backend
replicas: 2
template:
metadata:
labels:
app: redis
role: slave
tier: backend
spec:
containers:
- name: slave
image: gcr.io/google_samples/gb-redisslave:v3
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 6379
$ kubectl apply -f redis-slave-deployment.yaml
deployment.apps/redis-slave created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-master-6b54579d85-l8hsn 1/1 Running 0 7m10s
redis-slave-799788557c-599w2 1/1 Running 0 31s
redis-slave-799788557c-f2lc6 1/1 Running 0 32s
Service
マスターと同様に、スレーブ用にClusterIPをデプロイします。
apiVersion: v1
kind: Service
metadata:
name: redis-slave
labels:
app: redis
role: slave
tier: backend
spec:
ports:
- port: 6379
selector:
app: redis
role: slave
tier: backend
$ kubectl apply -f redis-slave-service.yaml
service/redis-slave created
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d
redis-master ClusterIP 10.101.227.29 <none> 6379/TCP 4m59s
redis-slave ClusterIP 10.109.67.11 <none> 6379/TCP 9s
フロントエンド
ゲストブックアプリケーションには、HTTPリクエストをサーブするPHPで書かれたウェブフロントエンドがあります。このアプリケーションは、書き込みリクエストに対してはredis-master Serviceに、読み込みリクエストに対してはredis-slave Serviceに接続するように設定されています。
Pod
Replica数3のPodをDeploymentでデプロイします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: guestbook
spec:
selector:
matchLabels:
app: guestbook
tier: frontend
replicas: 3
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google-samples/gb-frontend:v4
resources:
requests:
cpu: 100m
memory: 100Mi
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
$ kubectl apply -f frontend-deployment.yaml
deployment.apps/frontend created
$ kubectl get pods -l app=guestbook -l tier=frontend
NAME READY STATUS RESTARTS AGE
frontend-56fc5b6b47-fx4gv 1/1 Running 0 3m3s
frontend-56fc5b6b47-qblhv 1/1 Running 0 3m3s
frontend-56fc5b6b47-qlph7 1/1 Running 0 3m3s
Service
外部にゲストブックアプリケーションを公開するために、ここではLoadBalancerをデプロイします。
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
type: LoadBalancer
ports:
- port: 80
selector:
app: guestbook
tier: frontend
$ kubectl apply -f frontend-service.yaml
service/frontend created
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 10.99.203.0 10.20.30.150 80:31257/TCP 4s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d
redis-master ClusterIP 10.101.227.29 <none> 6379/TCP 16m
redis-slave ClusterIP 10.109.67.11 <none> 6379/TCP 11m
動作確認
外部からブラウザで接続し、ゲストブックアプリケーションの動作を確認します。
以下の画像のように接続と読み書きができるこが確認できます。
構築自体は公開されているマニフェストをapplyするだけですので簡単にできますが、それぞれの役割を確認しながら図に書くことでマイクロサービスの基本を学ぶことができました。
ロギングとメトリクスの追加
続けてもう一つのTutorialとして、作成したゲストブックアプリケーションにロギングとメトリクス機能を追加します。
このチュートリアルは、Redisを使用したPHPのゲストブックのチュートリアルを前提に作られています。Elasticが開発したログ、メトリクス、ネットワークデータを転送するオープンソースの軽量データシッパーであるBeatsを、ゲストブックと同じKubernetesクラスターにデプロイします。BeatsはElasticsearchに対してデータの収集、分析、インデックス作成を行うため、結果の運用情報をKibana上で表示・分析できるようになります。この例は、以下のコンポーネントから構成されます。
- Redisを使用したPHPのゲストブックの実行中のインスタンス
- ElasticsearchとKibana
- Filebeat
- Metricbeat
- Packetbeat
ClusterRoleBindingの追加
これから作成するkube-state-metricsとBeatsをkube-system namespaceにデプロイできるようにClusterRoleBindingを追加します。
$ kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=kosuke
clusterrolebinding.rbac.authorization.k8s.io/cluster-admin-binding created
$ kubectl describe clusterrolebindings.rbac.authorization.k8s.io cluster-admin-binding
Name: cluster-admin-binding
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: cluster-admin
Subjects:
Kind Name Namespace
---- ---- ---------
User kosuke
kube-state-metricsのインストール
Kubernetesのkube-state-metricsは、Kubernetes APIサーバーをlistenして、オブジェクトの状態に関するメトリクスを生成する単純なサービスです。
Red HatのOpenShiftではPrometheusのモニタリングで使われてますね。
1.1. クラスターモニタリングについて
Githubからクローンしてapplyします。
$ git clone https://github.com/kubernetes/kube-state-metrics.git kube-state-metrics
Cloning into 'kube-state-metrics'...
remote: Enumerating objects: 38, done.
remote: Counting objects: 100% (38/38), done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 19889 (delta 15), reused 24 (delta 10), pack-reused 19851
Receiving objects: 100% (19889/19889), 16.64 MiB | 3.81 MiB/s, done.
Resolving deltas: 100% (12629/12629), done.
$ kubectl apply -f kube-state-metrics/examples/standard
clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created
clusterrole.rbac.authorization.k8s.io/kube-state-metrics created
deployment.apps/kube-state-metrics created
serviceaccount/kube-state-metrics created
service/kube-state-metrics created
$ kubectl get pod -n kube-system kube-state-metrics-5c5cb55b4-pf5r5
NAME READY STATUS RESTARTS AGE
kube-state-metrics-5c5cb55b4-pf5r5 1/1 Running 0 31s
Elasticsearchの準備
ここでは、Elasticsearchのクラウドサービスを利用します。
https://cloud.elastic.co/
ログイン画面で、Googleでログインしました。
以下の画面になりますので、「Start your free tiral」をクリックします。
Elasticsearchをデプロイします。
Cloud Platformとリージョンを選択します。ここでは、GCPの東京リージョンを選択しました。
画像を取り忘れましたが、下にスクロールして名前を設定して「Create」みたいなボタンをクリックします。
そうすると以下の画面が表示されますので、パスワードをメモするかダウンロードします。
あとで、Secretを作成する際に使用します。
デプロイされるのをしばらく待ちます。
デプロイされると以下の画面になりますので、CloudIDをメモしておきます。
これでElasticsearchの準備は完了です。
Beatsのパッケージのダウンロード
Githubからクローンします。
ちなみに、400MBくらいあって、私の環境ではディスクフルになってやり直しました。
$ git clone https://github.com/elastic/examples.git
Cloning into 'examples'...
remote: Enumerating objects: 63, done.
remote: Counting objects: 100% (63/63), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 6738 (delta 22), reused 20 (delta 6), pack-reused 6675
Receiving objects: 100% (6738/6738), 124.89 MiB | 4.09 MiB/s, done.
Resolving deltas: 100% (3258/3258), done.
Checking out files: 100% (804/804), done.
$ cd examples/beats-k8s-send-anywhere/
Secretの作成
Elasticsearchを作成したときにメモしたパスワードとCloudIDを使用してSecretを作成します。
ダウンロードしたパッケージにあるファイルを編集します。念のため編集前にオリジナルをコピーしておきます。
$ cp ELASTIC_CLOUD_AUTH ELASTIC_CLOUD_AUTH.org
$ cp ELASTIC_CLOUD_ID ELASTIC_CLOUD_ID.org
コピーしたら、viなどで編集します。
編集したファイルを使って、Secretを作成します。
$ kubectl create secret generic dynamic-logging --from-file=./ELASTIC_CLOUD_ID --from-file=./ELASTIC_CLOUD_AUTH --namespace=kube-system
secret/dynamic-logging created
Beatsのデプロイ
マニフェストファイルはBeatごとに提供されます。これらのマニフェストファイルは、上で作成したSecretを使用して、BeatsをElasticsearchおよびKibanaサーバーに接続するように設定します。
Filebeatのデプロイ
Filebeatは、Kubernetesのノードと、ノード上で実行している各Pod内のコンテナから、ログを収集します。FilebeatはDaemonSetとしてデプロイされます。
マニフェストをapplyします。
$ kubectl apply -f filebeat-kubernetes.yaml
configmap/filebeat-dynamic-config created
clusterrolebinding.rbac.authorization.k8s.io/filebeat-dynamic created
clusterrole.rbac.authorization.k8s.io/filebeat-dynamic created
serviceaccount/filebeat-dynamic created
error: unable to recognize "filebeat-kubernetes.yaml": no matches for kind "DaemonSet" in version "extensions/v1beta1"
DaemonSetでエラーになりました。APIバージョンが古いようです。
Kubernetes v1.18のDaemonSetのAPIバージョンを確認します。
$ kubectl explain DaemonSet | head
KIND: DaemonSet
VERSION: apps/v1
DESCRIPTION:
DaemonSet represents the configuration of a daemon set.
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
マニフェストを以下のように編集しました。
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-dynamic-config
namespace: kube-system
labels:
k8s-app: filebeat-dynamic
kubernetes.io/cluster-service: "true"
data:
filebeat.yml: |-
setup.dashboards.enabled: true
setup.template.enabled: true
setup.template.settings:
index.number_of_shards: 1
filebeat.modules:
- module: system
syslog:
enabled: true
#var.paths: ["/var/log/syslog"]
auth:
enabled: true
#var.paths: ["/var/log/authlog"]
filebeat.autodiscover:
providers:
- type: kubernetes
templates:
- condition.equals:
kubernetes.labels.app: redis
config:
- module: redis
log:
input:
type: docker
containers.ids:
- ${data.kubernetes.container.id}
slowlog:
enabled: true
var.hosts: ["${data.host}:${data.port}"]
- condition.contains:
kubernetes.labels.tier: frontend
config:
- module: apache2
access:
input:
type: docker
containers.ids:
- ${data.kubernetes.container.id}
error:
input:
type: docker
containers.ids:
- ${data.kubernetes.container.id}
- condition.equals:
kubernetes.labels.app: mysql
config:
- module: mysql
error:
input:
type: docker
containers.ids:
- ${data.kubernetes.container.id}
slowlog:
input:
type: docker
containers.ids:
- ${data.kubernetes.container.id}
processors:
- drop_event:
when.or:
- and:
- regexp:
message: '^\d+\.\d+\.\d+\.\d+ '
- equals:
fileset.name: error
- and:
- not:
regexp:
message: '^\d+\.\d+\.\d+\.\d+ '
- equals:
fileset.name: access
- add_cloud_metadata:
- add_kubernetes_metadata:
- add_docker_metadata:
cloud.auth: ${ELASTIC_CLOUD_AUTH}
cloud.id: ${ELASTIC_CLOUD_ID}
output.elasticsearch:
hosts: ${ELASTICSEARCH_HOSTS}
username: ${ELASTICSEARCH_USERNAME}
password: ${ELASTICSEARCH_PASSWORD}
setup.kibana:
host: ${KIBANA_HOST}
---
apiVersion: apps/v1 #changed
kind: DaemonSet
metadata:
name: filebeat-dynamic
namespace: kube-system
labels:
k8s-app: filebeat-dynamic
kubernetes.io/cluster-service: "true"
spec:
selector: #added
matchLabels: #added
k8s-app: filebeat-dynamic #added
template:
metadata:
labels:
k8s-app: filebeat-dynamic
kubernetes.io/cluster-service: "true"
spec:
serviceAccountName: filebeat-dynamic
terminationGracePeriodSeconds: 30
containers:
- name: filebeat-dynamic
image: docker.elastic.co/beats/filebeat:7.6.2
imagePullPolicy: Always
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
env:
- name: ELASTIC_CLOUD_ID
valueFrom:
secretKeyRef:
name: dynamic-logging
key: ELASTIC_CLOUD_ID
optional: true
- name: ELASTIC_CLOUD_AUTH
valueFrom:
secretKeyRef:
name: dynamic-logging
key: ELASTIC_CLOUD_AUTH
optional: true
- name: ELASTICSEARCH_HOSTS
valueFrom:
secretKeyRef:
name: dynamic-logging
key: ELASTICSEARCH_HOSTS
optional: true
- name: KIBANA_HOST
valueFrom:
secretKeyRef:
name: dynamic-logging
key: KIBANA_HOST
optional: true
- name: ELASTICSEARCH_USERNAME
valueFrom:
secretKeyRef:
name: dynamic-logging
key: ELASTICSEARCH_USERNAME
optional: true
- name: ELASTICSEARCH_PASSWORD
valueFrom:
secretKeyRef:
name: dynamic-logging
key: ELASTICSEARCH_PASSWORD
optional: true
securityContext:
runAsUser: 0
volumeMounts:
- name: config
mountPath: /etc/filebeat.yml
readOnly: true
subPath: filebeat.yml
- name: data
mountPath: /usr/share/filebeat/data
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: dockersock
mountPath: /var/run/docker.sock
volumes:
- name: config
configMap:
defaultMode: 0600
name: filebeat-dynamic-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: dockersock
hostPath:
path: /var/run/docker.sock
- name: data
emptyDir: {}
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat-dynamic
subjects:
- kind: ServiceAccount
name: filebeat-dynamic
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat-dynamic
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: filebeat-dynamic
labels:
k8s-app: filebeat-dynamic
rules:
- apiGroups: [""] # "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat-dynamic
namespace: kube-system
labels:
k8s-app: filebeat-dynamic
編集したマニフェストで再度applyします。
$ kubectl apply -f filebeat-kubernetes.yaml
configmap/filebeat-dynamic-config unchanged
daemonset.apps/filebeat-dynamic created
clusterrolebinding.rbac.authorization.k8s.io/filebeat-dynamic unchanged
clusterrole.rbac.authorization.k8s.io/filebeat-dynamic unchanged
serviceaccount/filebeat-dynamic unchanged
$ kubectl get pod -n kube-system -l k8s-app=filebeat-dynamic
NAME READY STATUS RESTARTS AGE
filebeat-dynamic-2bxv7 1/1 Running 0 119s
filebeat-dynamic-bvs22 1/1 Running 0 119s
今度は成功しました。
Metricbeatのデプロイ
Metricbeatの自動検出はFilebeatと同じ方法で設定します。
FIleBeatと同様にAPIのバージョンが古いので、マニフェストを編集します。DaemonSetとDeploymentの以下を編集します。
- APIバージョンの変更
- selectorの追加
$ kubectl apply -f metricbeat-kubernetes.yaml
configmap/metricbeat-setup-config created
job.batch/metricbeat-setup created
configmap/metricbeat-daemonset-config created
configmap/metricbeat-daemonset-modules created
daemonset.apps/metricbeat created
configmap/metricbeat-deployment-config created
configmap/metricbeat-deployment-modules created
deployment.apps/metricbeat created
clusterrolebinding.rbac.authorization.k8s.io/metricbeat created
clusterrole.rbac.authorization.k8s.io/metricbeat created
serviceaccount/metricbeat created
$ kubectl get pods -n kube-system -l k8s-app=metricbeat
NAME READY STATUS RESTARTS AGE
metricbeat-5b9dfd9f8d-t7j7t 1/1 Running 0 2m
metricbeat-vs7rf 1/1 Running 0 2m
metricbeat-wznq4 1/1 Running 0 2m
Packetbeatのデプロイ
Packetbeatの設定は、FilebeatやMetricbeatとは異なります。コンテナのラベルに対するパターンマッチを指定する代わりに、関連するプロトコルとポート番号に基づいた設定を書きます
同様にDaemonSetのAPIバージョンの変更とselectorを追加してapplyします。
kubectl apply -f packetbeat-kubernetes.yaml
configmap/packetbeat-dynamic-config created
daemonset.apps/packetbeat-dynamic created
clusterrolebinding.rbac.authorization.k8s.io/packetbeat-dynamic created
clusterrole.rbac.authorization.k8s.io/packetbeat-dynamic created
serviceaccount/packetbeat-dynamic created
これで完了です。
Kibanaでの確認
ElasticsearchとKibanaをほとんど使ったことがないので、これで正しいのかわからないのですが、ログは収集できているようです。
簡単にできますね。
まとめ
今回はマニュアルのTutorialを実際にやってみました。
構築自体はマニュアル通りにやるだけですので簡単ですが、実際のアーキテクチャなどを整理しながらやることで理解が深まりました。
ElasticsearchとKibanaは、「とりあえずできた」状態でまだ全然わかってないので、無料トライアルの間にもう少し触ってみたいと思います。あと10日。