etcdに格納されたデータを見る

kubernetesがリソースを作成するにあたり、大きく二つのステップがある。

  1. apiを経由してetcdにデータが格納される
  2. controllerがapiを経由しetcdのデータを突合し、差分があれば更新(Reconciliation Loop)

f:id:wadason:20220223235045p:plain https://kubernetes.io/ja/docs/concepts/overview/components/

リソースを変更・追加するときはetcdにデータが格納される。 コアコンセプトであるReconciliation Loopでetcdと実際のリソースとの差分を検知しリソースを更新することで実現できる。

この記事では前半のkubectlを実行し、etcdにデータが格納されるまでを確認することにした。 Reconciliation Loopの挙動は別途確認することにする。

今回は以下を確認する。

  • etcdにデータが格納されるまで
  • etcdのデータの中身

前提

  • kubernetes v1.22.3
  • minikube v1.24.0
  • etcdctl 3.3.12

etcdにデータが格納されるまで

kubectl applyで実行する。

まずはマニフェストを準備する。 最近検証で使ってたので、dnsutilsを使っているが特に意味はない。

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
spec:
  containers:
  - name: dnsutils
    image: k8s.gcr.io/e2e-test-images/jessie-dnsutils:1.3
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

podを立ち上げる。

# k apply -f dnsutils.yaml
# k get po

NAME       READY   STATUS    RESTARTS   AGE
dnsutils   1/1     Running   0          8m32s

余談

kubectlは最終的にapiを実行するので、 クラスタに接続できるようにして、直接apiを実行すると同じことが実現できる。

kubectl proxy
Starting to serve on 127.0.0.1:8001

curl -X GET http://127.0.0.1:8001/api/v1/namespaces/default/pods
# イメージだけ伝わればいいので、所々省略してる
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "13267"
  },
  "items": [
    {
      "metadata": {
        "name": "dnsutils",
        "namespace": "default",
        "uid": "c6c5612f-adbe-43b6-8ff6-9dc63381d6b0",
        "resourceVersion": "12346",
        "creationTimestamp": "2022-02-23T05:56:42Z",
        # 省略
      },
      "spec": {
        # 省略
      },
      "status": {
        # 省略
      }
    }
  ]
}%

こんな感じで、RESTでkubectlに備わってる機能を実行できる。 無数に存在するapiの中で開発に必要な機能を抜粋し、kubectlで叩けるようになっていることがわかる。

api-serverの挙動も今回はソースコード追ったりはしない。 が、etcdにデータが格納される流れとしては、以下のようになる。

  • リクエストを受信
  • ServerChainにより前処理を実行
  • マニフェストをパースしてバリデーションを実行(エラーならレスポンスを返却)
  • バリデーションが通ればetcdに保存
  • レスポンスを返却

etcdのデータを見てみる

今回はetcdctlを用いて検証したが、curlなどのhttpリクエストでも同じことが実現できる。

その前にetcdについて

  • etcdとは、キーバリューストアのデータベースである。
  • kubernetesのデータ(状態や構成、メタデータ)を保持している。
  • Raftという合意アルゴリズムを用いて、可用性と一貫性を担保している。

Raftの合意アルゴリズムの特徴としては、可用性を高めるために必ずリーダが選出され、フォロワにログをレプリケートする。 例えばリーダが正常に稼働しなくなった場合は次のリーダが選出される(Leader Electionという)。

難しくてあんまり理解できてないが、 高い可用性と一貫性を担保している仕組みという理解に止めておく。

参考

https://raft.github.io/

kubernetesの場合は、マスターノードの数だけetcdを用意する「stacked etcd」という構成が一般的。 f:id:wadason:20220224103319p:plain

※ etcdをマスターノードではなく外部で構築し運用する場合は「external etcd」という構成になる。 参考

kubernetes.io

minikubeに接続しetcdctlで検証

$ minikube ssh
$ docker run -it --net host gcr.io/etcd-development/etcd /bin/sh
$ etcdctl version

etcdctl version: 3.3.12
API version: 3.3

やり方は下記を参考にした。 - qiita.com

まずは、必要な環境変数を設定(今回はminikubeの場合)。

export ETCDCTL_API=3
export ETCDCTL_CACERT=/var/lib/minikube/certs/etcd/ca.crt
export ETCDCTL_CERT=/var/lib/minikube/certs/apiserver-etcd-client.crt
export ETCDCTL_KEY=/var/lib/minikube/certs/apiserver-etcd-client.key

試しにetcdクラスタの一覧を取得。 今回はマスターノードが1台のため一つだけ表示されている。

/ # etcdctl member list
aec36adc501070cc, started, minikube, https://192.168.49.2:2380, https://192.168.49.2:2379

keyvalueストアなので、keyを全て出力してみる。

etcdctl get --keys-only  --prefix ""

長すぎるので省略するが、例えばnamespaceの場合。

/registry/namespaces/default
/registry/namespaces/kube-node-lease
/registry/namespaces/kube-public
/registry/namespaces/kube-system

kubectlで試しに取得すると同じ結果が出力されている。

k get namespaces
NAME              STATUS   AGE
default           Active   9h
kube-node-lease   Active   9h
kube-public       Active   9h
kube-system       Active   9h

適当にkeyを指定して、valueを参照する。

# etcdctl get "/registry/namespaces/default" -w=json
{
   "header":{
      "cluster_id":18038207397139142846,
      "member_id":12593026477526642892,
      "revision":15809,
      "raft_term":3
   },
   "kvs":[
      {
         "key":"Zm9v",
         "create_revision":203,
         "mod_revision":203,
         "version":1,
         "value":"Zm9v"
      }
   ],
   "count":1
}

さっき作った、podはどんな感じかというと、

etcdctl get --keys-only  --prefix "" | grep dnsutils
/registry/events/default/dnsutils.16d654aa24560992
/registry/events/default/dnsutils.16d654aa27a5029a
/registry/events/default/dnsutils.16d654aa2e24e48b
/registry/events/default/dnsutils.16d66d970a929b22
/registry/events/default/dnsutils.16d66d9748aec6fc
/registry/events/default/dnsutils.16d66d974aa74a95
/registry/events/default/dnsutils.16d66d97548d2181
/registry/pods/default/dnsutils

eventと、podのリソースが存在する。 適当に見てみると、

etcdctl get "/registry/events/default/dnsutils.16d654aa24560992" -w=json
{
   "header":{
      "cluster_id":18038207397139142846,
      "member_id":12593026477526642892,
      "revision":15809,
      "raft_term":3
   },
   "kvs":[
      {
         "key":"Zm9v",
         "create_revision":203,
         "mod_revision":203,
         "version":1,
         "value":"Zm9v"
      }
   ],
   "count":1
}

etcdctl get "/registry/pods/default/dnsutils" -w=json
{
   "header":{
      "cluster_id":18038207397139142846,
      "member_id":12593026477526642892,
      "revision":15972,
      "raft_term":3
   },
   "kvs":[
      {
         "key":"Zm9v",
         "create_revision":4742,
         "mod_revision":15215,
         "version":14,
         "value":"Zm9v"
      }
   ],
   "count":1
}

ちゃんとデータが格納されとる。

試しに、新しいkeyを作成してデータを入れてみる。

/ # etcdctl put /test/hoge "hoge"
OK
/ # etcdctl get /test/hoge
/test/hoge

さらに、バージョン管理も確認してみる。

/ # etcdctl put /test/piyo piyo
OK
/ # etcdctl put /test/piyo2 piyo2
OK
/ # etcdctl put /test/piyo3 piyo3
OK
/ # etcdctl get /test/piyo --prefix
/test/piyo
piyo
/test/piyo2
piyo2
/test/piyo3
piyo3
/ # etcdctl get /test/piyo --prefix --sort-by=CREATE
/test/piyo
piyo
/test/piyo2
piyo2
/test/piyo3
piyo3

etcdctl get /test/piyo --rev=0
/test/piyo
piyo

まとめ

etcdの中身を確認すると、kubectlやapiがなにをしているのかイメージがつきやすい。 keyはetcdctl putからもわかるように自由に作成できる。

データ格納後は、各種コントローラーが、apiを経由してetcdの変更差分を検知し、schedulerにより実際の変更を行う流れになる。 この拡張性が、カスタムコントローラを開発しやすくすることにつながってる、と現時点では感じた。

etcdが更新される場合に、コントローラーがどのように差分を検知し、処理を行うのかも気になるので今後検証してみる。

参考

AWSでwowzaを構築(EC2) + live配信(RTSP/HLS)を検証

モチベーション

  • wowzaって実際どうなっているのかみてみたい
    • 構築方法が知りたい
    • どんな機能があるか知りたい
    • 料金どれくらいなのか知りたい
  • live配信を任意のアプリでできるか確認したい
    • RTSPで配信したい

※実際検証したのは2ヶ月前、その時検証したことを記載してます。

前提

  • AWS Marketplaceにある「Wowza Streaming Engine (Linux PAID)」を利用
  • live配信はIOS版の「LiveReporter」を利用
  • 今回は大人の事情でプロトコルにRTSPを利用

検証

1. AWS marketplaceでwowzaを購入とセットアップ

1.1 とりあえず購入

購入した後の料金は、「Estimating your costs」でざっと確認することが可能。 m4.largeが現状選べる中で最も小さいインスタンスだった。今回は検証用なのでこれで良い。

これで、m4.largeのインスタンス料金($0.419/hr) + サブスクリプション費用($15/m)の費用がかかるそう。 EC2の料金($0.129/hr)の他に、ソフトウェアライセンス費用($0.29/hr)もかかるみたい。

参考

region → tokyo
Fulfillment Option → 64-bit(x86)
instance type → m4.large

購入前の設定 f:id:wadason:20200816011541p:plain Wowza Streaming Engine (Linux PAID)

起動 f:id:wadason:20200816011717p:plain

起動時のオプションは下記のように設定した。

Choosee Action : Launch from Website
EC2 Instance Type : m4.large
VPC Settings : 普段使ってるVPCID
Subnet Settings : ↑のvpc内のsubnet
Security Group Settings : 検証のため全開け
Key Pair Settings : 普段使ってる鍵

→ 「Launch」を押下してしばらく待つとインスタンスが起動する。めちゃ便利。

f:id:wadason:20200816011736p:plain

ここからアクセスして、ログイン(wowza/${instance_id})。ログイン後にid/passwordを変更してセットアップ完了。

http://${public_dns}:8088/enginemanager

2.「LiveReporter」でRTSPを利用してLive配信してみる

準備

上のpublic dnsにアクセスして、ヘッダ部分の「Applications」を選択。右上の「Test Players」を選択。「Mobile」タグにIOS,Android/Otherそれぞれのurlが記載されている。今回は、Android/Otherのurlをメモっとく(rtsp://${public_ip}:1935/test/myStream)。

LiveReporterをインストールして、「設定」→ 「RTSP」を選択。 インストール先

こんな感じで接続できる。パスワード求められたらさっきwowzaで再設定したid/passwordを設定すれば良い。 f:id:wadason:20200816011800p:plain

その後同じモーダルの、「AppleHLS」の再生ボタンを押すとlive配信ができていることがわかる。 f:id:wadason:20200816011835p:plain

3.せっかくなのでwowzaの気になった機能をいろいろみてみる

Monitoring

通信プロトコルごとのConnectionsと、Networkを確認できる。

network f:id:wadason:20200816011855p:plain

connections f:id:wadason:20200816011912p:plain

Sources

サポートされているエンコーダーを指定して、接続するための設定を教えてくれる。いろんな会社のエンコーダーがあるみたい。

Incoming Streams

wowzaを使って配信されている動画を確認できる。

Stream Targets

配信するストリーミングの対象を追加できる。

Source Security

認証などの設定。

Transcoder

ファイル変換のツール。課金が必要。

検証のまとめ

  • 5分くらいでwowzaはセットアップできた。
  • インスタンスタイプが最低m4.largeとまあまあでかい。
  • RTSPでどうやってpush型で配信しているんだろう?

EC2で直接構築したprometheus/alert managerをECSに移行した時に考えたこと(AWS.ECSで監視)

モチベーション

元々amazon-linuxにprometheusをぶち込んで監視していた。prometheusを実際に運用するとなると、

  • 俗人化を防ぎたい
  • AWSのクレデンシャルの管理
  • どの設定ファイルを上書すれば良いか
  • ローカルでも動作確認したい

という課題/要望が出てきた。現場に適用して3ヶ月経って、現在行っている工夫やprometheusのCI/CD周り、設定ファイルの管理方法などをまとめておく。

前提

  • 監視対象のタスク定義には、exporterが入っている。
  • 今回はECSで稼働しているサービスに焦点を当てる。
  • alert managerやprometheusの具体的な設定やパラメータの意味は省略する。

実現したこと

  • prometheus/alertmanagerの設定ファイルが安全に管理され、開発者が編集/閲覧可能な状態。
  • スケールしたインスタンスをサービスディスカバリで検知し、モニタリング可能な状態(grafanaなどで)。

やったこと

exporter編

下準備として、exporterをタスク定義のコンテナに追加する。 containerDefinitionsにprom/node_exporterを追加する。SGのport開放も忘れずに行っておく(9100)。

今回は、公式のdocker imageであるprom/node-exporterを設定した。

{
  "containerDefinitions": [
    {
      // アプリケーションのコンテナ定義
      ...
    },
    {
      // node_exporterのコンテナ定義
      "dnsSearchDomains": null,
      "environmentFiles": null,
      "logConfiguration": null,
      "entryPoint": null,
      "portMappings": [
        {
          "hostPort": 9100,
          "protocol": "tcp",
          "containerPort": 9100
        }
      ],
      "command": null,
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": null,
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "prom/node-exporter",
      "startTimeout": null,
      "firelensConfiguration": null,
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": null,
      "name": "node-exporter"
    }
  ],
  ...
}

タスクを起動して、アクセスできたらOK.

$ curl ${public_ip}:9100/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 1.3784e-05
go_gc_duration_seconds{quantile="0.25"} 1.3784e-05
go_gc_duration_seconds{quantile="0.5"} 1.6655e-05
go_gc_duration_seconds{quantile="0.75"} 1.6655e-05
go_gc_duration_seconds{quantile="1"} 1.6655e-05
go_gc_duration_seconds_sum 3.0439e-05
go_gc_duration_seconds_count 2

prometheus編

本題。

githubで管理するにあたり

ざっくり下記を工夫した。

  • AWSのクレデンシャルは含めない
  • 最低限の設定ファイルだけを管理する
  • 設定ファイルに含まれるクレデンシャルはdocker build時に変数を適用する

ローカルで動作確認したい場合には、docker-compose.yamlをtemplateから作成して(.gitignoreしている)、envにAWSのクレデンシャル情報を記載する。それでもミスるので、git-secretは入れた方がいいと思う。

docker build時に、envsubstを利用して、各yamlの任意の変数を、環境変数の値に適用する仕組みにしている。

ディレクトリ構成

├── _alert.dockerfile            // alert manager
├── _prom.dockerfile             // prometheus
├── alert_manager
│   └── template.config.yaml     // alert managerが参照する通知先の設定
├── prom
│   ├── rules.yaml               // alert条件の設定
│   └── template.prometheus.yml  // prometheusのメトリクス監視の設定 & サービスディスカバリ
└── template.docker-compose.yaml

prometheus.yaml

AWSの変数はenvsubstで上書きされる。

global:
  scrape_interval:     15s 
  evaluation_interval: 15s 
  external_labels:
      monitor: 'monitor'

rule_files:
  - rules.yaml
alerting:
  alertmanagers:
    - scheme: http
      static_configs:
      - targets:
        - alertmanager:9093

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets:
        -  prometheus:9090
        -  node-exporter:9100
  - job_name: 'AWS-RESOUCES'
    ec2_sd_configs:
      - region: ${AWS_REGION}
        access_key: ${AWS_ACCESS_KEY}
        secret_key: ${AWS_SECRET_KEY}
        port: 9100
    # publicIPで取得したい場合は下記を適用する
    relabel_configs:
      - source_labels: [__meta_ec2_public_ip]
        regex:  '(.*)'
        target_label: __address__
        replacement: '${1}:9100'
      - source_labels: [__meta_ec2_tag_Name]
        target_label: instance

rules.yaml

groups:
  - name: targets
    rules:
    - alert: monitor_service_down
      expr: up == 0
      for: 30s
      labels:
        severity: critical
      annotations:
        summary: "Monitor service non-operational"
        description: "Service {{ $labels.instance }} is down."

alert.conf

めんどくさくなって、${WEBHOOK}はべた書きで試したけど、envsubstで同様に適用することも可能なはず。

global:
  slack_api_url: '${WEBHOOK}'

route:
  receiver: 'slack'

receivers:
  - name: 'slack'
    slack_configs:
    - channel: '#alerts'
      text: "{{ .CommonAnnotations.summary }}"
      send_resolved: true

ローカルで動作確認

template.docker-compose.yamlからdocker-compose.yamlを作成して、environmentにクレデンシャル情報を記載する。 prometheusのサービスディスカバリはそのままローカルでも適用される(public subnetの場合だけかも)。

# ここを書き換える
environment:
  AWS_REGION: ${AWS_REGION}
  AWS_ACCESS_KEY: ${AWS_ACCESS_KEY}
  AWS_SECRET_KEY: ${AWS_SECRET_KEY}

# buildして起動
$ cd /path/to/repository/
$ docker-compose build
$ docker-compose up

deploy

docker-compose buildで生成された、各イメージ(${REPO}_prometheus,${REPO}_alertmanager)をpush。

docker-compose build
docker tag ${image_name}:latest ${URI}/${image_name}:latest
docker push ${URI}/${image_name}:latest

タスク定義の実行コマンドは下記のようになる、

prometheusの場合

エントリポイント ["sh","-c"]
コマンド ["envsubst < /etc/prometheus/template.prometheus.yml > /etc/prometheus/prometheus.yml ; /bin/prometheus --config.file=/etc/prometheus/prometheus.yml --web.console.libraries=/usr/share/prometheus/console_libraries --web.console.templates=/usr/share/prometheus/consoles"]

ポートマッピング:9090→9090 tcp

# 環境変数も設定しておくこと
- AWS_ACCESS_KEY
- AWS_REGION
- AWS_SECRET_KEY

alertmanagerの場合

エントリポイント ["sh","-c"]
コマンド ["--config.file=/etc/alertmanager/config.yaml"]

ポートマッピング:9093→9093 tcp

まとめ

成果物は下記にまとめておきました。 また課題が見つかり次第、いろいろ試そうと思っています。

wadason/prom_on_ecs_example

参考

envsubst環境変数を適用する方法など

https://cross-black777.hatenablog.com/entry/2017/10/30/221644