Upgrade to Pro — share decks privately, control downloads, hide ads and more …

gitops-using-flux-and-gitlab

endo-k
September 13, 2018

 gitops-using-flux-and-gitlab

「GitLab Meetup Tokyo #10: Summit Aug 2018 Recap」(https://gitlab-jp.connpass.com/event/98160/)こちらのイベントで発表させていただいた資料です

タイトル:GitLabでWeave FluxでGitOpsしてみた

endo-k

September 13, 2018
Tweet

More Decks by endo-k

Other Decks in Technology

Transcript

  1. GitLab と Weave Flux で
    GitOps してみた
    GitLab Meetup Tokyo #10: Summit Aug 2018 Recap

    View Slide

  2. 自己紹介
    ● 遠藤 耕平
    ● 株式会社ぐるなび
    ○ 2010年入社
    ○ サーバインフラエンジニア
    ○ 明日で最後(お世話になりました!)

    View Slide

  3. はじめに
    ● 本資料について
    ○ 所属組織の公式見解とは一切関係ありません
    ○ 「GitOpsの全貌を解説!」という内容ではありません
    ○ 「一緒にGitOpsの扉を開きましょう」(意見交換したいです)
    ○ 「Weave FluxというのはGitHubだけじゃなくてGitLabでも使えるんだ」を感じてもらいたいです
    ● 資料は後ほど公開します
    ● 所用のため懇親会まで残れません。。 m(_ _)m

    View Slide

  4. Contents
    ● 本日のテーマ
    ● 背景
    ● GitOpsとは
    ● Weave Flux
    ● GitLabとFlux
    ● Setup Flux
    ● GitOpsしてみる
    ● Tips
    ● 使ってみた感想
    ● まとめ

    View Slide

  5. 本日のテーマ

    View Slide

  6. https://gitlab-jp.connpass.com/event/98160/

    View Slide

  7. 具体的には...
    ● KubernetesのmanifestファイルのGit管理について
    ● Git管理されたmanifestファイルを組み込んだ CI/CD Pipeline
    ● その流れで辿り着いた GitOpsとは
    ● GitOpsを実現するWeave FluxとGitLabの連携について

    View Slide

  8. ※補足
    ● manifestファイルとは、Kubernetes上でコンテナを起動するために定義された YAMLファイル
    ● kubectlコマンドを利用すると manifestファイルをKubernetesにデプロイできる
    ● Canary, Blue/Green などの機能を持った SpinnakerなどのCDツールもいろいろ

    View Slide

  9. 会場内アンケート
    1. Kubernetesを個人/会社で利用したことがある人?
    2. manifestファイルをGit管理している人?
    3. Git管理されたmanifestファイルをCI/CD Pipelineに組み込めている人?

    View Slide

  10. 背景

    View Slide

  11. Git Repos
    Apps Code
    KubernetesのCI/CD Pipelineについて考えた①
    Tests Deploy
    Container
    Images
    初期テンプレートのk8s manifestをデプロイ後、Image Tag
    のみ修正していくパターンだと...

    View Slide

  12. Git Repos
    Apps Code
    KubernetesのCI/CD Pipelineについて考えた①
    Tests Deploy
    Container
    Images
    Image Tag以外のものを更新する場合はPipeline外となって
    しまう。すると、いつ誰がどんな変更を、誰の許可を得てデプ
    ロイしたのか一切残らない
    kubectl apply

    View Slide

  13. Git Repos
    Apps Code
    KubernetesのCI/CD Pipelineについて考えた①
    Tests Deploy
    Container
    Images
    現在の状態のk8s manifestがないと、クラスタの移行やク
    ラッシュによる再構築に伴う正しいmanifestの再デプロイが
    困難
    crash..
    Migrate

    View Slide

  14. KubernetesのCI/CD Pipelineについて考えた① ~対策~
    ● KubernetesのmanifestをGit管理する
    ○ いつ誰が何を変更したのか
    ○ 誰が変更を許可したのか
    ○ ロールバックは容易か
    ○ クラスタの移行やクラッシュ時の再デプロイが可能か
    ● Git管理したmanifestを正とする
    ● Pipeline以外からのデプロイは禁止
    ○ 必ずGit管理を通じた履歴を残す
    ○ RBACで権限管理
    ● ここは規模やポリシー、文化などにより異なりそうなので情報交換したい

    View Slide

  15. KubernetesのCI/CD Pipelineについて考えた②
    Tests Deploy
    Container
    Images
    アプリケーションのコードとKubernetes manifestを一緒
    にGit管理してしまうと...
    kubectl apply
    Git Repos
    Apps Code
    k8s manifest

    View Slide

  16. KubernetesのCI/CD Pipelineについて考えた②
    Tests Deploy
    Container
    Images
    manifestの変更(たとえばConfigMap)だけでアプ
    リケーションの中身は変わっていないのにImageが
    ビルドされてしまう
    kubectl apply
    Git Repos
    Apps Code
    k8s manifest

    View Slide

  17. KubernetesのCI/CD Pipelineについて考えた② ~対策~
    ● アプリケーションのコードと Kubernetes manifestのリポジトリを分離する
    a. アプリケーションコードの修正 → Imageのビルドを目標
    b. manifestの修正 → Kubernetesクラスタへのデプロイを目標

    View Slide

  18. KubernetesのCI/CD Pipelineについて考えた③
    Tests
    Deploy
    Container
    Images
    kubectl apply
    Git Repos
    Apps Code
    Git Repos
    k8s manifest
    pull
    アプリケーションのコードとKubernetes manifestのGit管理を分けることで境界がスッキリ
    Merge
    Request

    View Slide

  19. KubernetesのCI/CD Pipelineについて考えた③
    ● 全てのオペレーションを Git起点(git push)にできる
    ● いつ誰が何を変更し、誰が承認してデプロイされたのか把握できる
    ● 役割に応じてアプリケーションコードと Kubernetes manifestの権限を分けることができる

    View Slide

  20. とある不安..
    Tests
    Deploy
    Container
    Images
    kubectl apply
    Git Repos
    Apps Code
    Git Repos
    k8s manifest
    pull
    『Git管理されている状態』と『実際のKubernetesクラスタの状態』は、本当にイコールか???
    Merge
    Request

    View Slide

  21. とある不安..
    Tests
    Deploy
    Container
    Images
    kubectl apply
    Git Repos
    Apps Code
    Git Repos
    k8s manifest
    pull
    ここのラインが制御できているか?
    (運用上、本当に制御可能か?)
    Merge
    Request

    View Slide

  22. とある不安.. ~対策~
    ● 『Git管理されている状態』と『実際の Kubernetesクラスタ上の状態』を比較・監視する
    ● 異なっていた場合、Git管理されているmanifestを正とし、以下アクションを行う
    a. 差分検知のアラート
    b. 差分を修正

    View Slide

  23. とある不安.. ~対策~
    ● 『Git管理されている状態』と『実際の Kubernetesクラスタ上の状態』を比較・監視する
    ● 異なっていた場合、Git管理されているmanifestを正とし、以下アクションを行う
    a. 差分検知のアラート
    b. 差分を修正
       ..
       ....
       ………... → GitOpsに行き当たる

    View Slide

  24. GitOpsとは

    View Slide

  25. What Is GitOps
    ● “GitOps is a way to do Continuous Delivery, it works by using Git as a source of truth for
    declarative infrastructure and workloads. For Kubernetes this means using git push instead of
    kubectl create/apply or helm install/upgrade.”

    ● “In the “GitOps” model, we use Git to solve for divergence and convergence, aided by a set of “diff”
    and “sync” tools that compare intended with actual state.”

    View Slide

  26. How GitOps
    “diff” and “sync” tools
    ● “diff”:kubediff
    ○ Git上のmanifestと稼働中のKubernetesクラスタ上の差分を確認する
    ○ pythonスクリプトも提供されているので、コマンドラインでの実行も可
    ○ PrometheusのAlert ruleで「一定時間、差分が解消されなかったらSlack通知」など
    ○ 今日はkubediffについて細かく触れませんが、GitLab上にあるmanifestとの比較も可
    ● “sync”:Weave Flux
    ○ 後述のページで説明

    View Slide

  27. And more..
    ※GitOpsの全てを説明する時間はないので、興味がある方は以下をご参照ください
    ● Posts in category gitops

    ● Git push all the things

    ● Modern best practices for high velocity app dev using cloud native tools
    ○ native-tools>

    View Slide

  28. Weave Flux

    View Slide

  29. Weave Flux
    ● GitOpsを提唱したWeaveworks によって開発

    ● “The GitOps Kubernetes operator”
    ● Git上のmanifest(YAML)とk8sクラスタの状態が自動的に一致することを保証
    ○ Git上のmanifest(YAML)を更新したら自動デプロイ
    ○ 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新
    ○ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    ● 2018年9月時点の最新版は v1.6.0

    View Slide

  30. Weave Flux for deployment

    View Slide

  31. GitLabとFlux

    View Slide

  32. GitLabのカバー範囲

    View Slide

  33. 今日お話しする連携ポイント

    View Slide

  34. Setup Flux

    View Slide

  35. Install & Settings
    ● RBAC適用
    ● memcachedのデプロイ
    ● GitLab接続用の秘密鍵をSecretで作成
    ● flux-deployment.yamlの修正
    ○ Fluxの監視対象にするGitリポジトリURLを指定
    ● fluxのデプロイ
    ● known_hostsの設定 & ConfigMapでの保存
    ● flux-deployment.yamlの再修正
    ○ ConfigMapの設定有効
    ○ (optional) プロキシ設定
    ○ (optional) fluxdが参照するnamespaceを制限する
        (--k8s-namespace-whitelist)* experimental
    ● fluxの再デプロイ

    View Slide

  36. プライベートリポジトリを利用する場合
    ● RBAC適用
    ● memcachedのデプロイ
    ● GitLab接続用の秘密鍵をSecretで作成
    ● flux-deployment.yamlの修正
    ○ Fluxの監視対象にするGitリポジトリURLを指定
    ● fluxのデプロイ
    ● known_hostsの設定 & ConfigMapでの保存
    ● flux-deployment.yamlの再修正
    ○ ConfigMapの設定有効
    ○ (optional) プロキシ設定
    ○ (optional) fluxdが参照するnamespaceを制限する
        (--k8s-namespace-whitelist)* experimental
    ● fluxの再デプロイ

    View Slide

  37. イメージの自動更新
    ● fluxctlのインストール(※optional)
    ● イメージの自動更新設定の有効化(※ annotation指定でも可)
    $ export FLUX_FORWARD_NAMESPACE=flux-test
    $ fluxctl list-controllers -n flux-test
    CONTROLLER CONTAINER IMAGE RELEASE POLICY
    flux-test:deployment/flux-test flux-test gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1 ready
    flux-test:deployment/flux flux quay.io/weaveworks/flux:1.5.0 ready
    flux-test:deployment/memcached memcached memcached:1.4.25 ready
    $ fluxctl automate --controller=flux-test:deployment/flux-test
    $ fluxctl list-controllers -n flux-test
    CONTROLLER CONTAINER IMAGE RELEASE POLICY
    flux-test:deployment/flux-test flux-test gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1 ready automated
    flux-test:deployment/flux flux quay.io/weaveworks/flux:1.5.0 ready
    flux-test:deployment/memcached memcached memcached:1.4.25 ready

    View Slide

  38. プライベートなコンテナレジストリの利用
    ● GitLab Container Registryも利用可
    ● 特にFluxで意識する設定はない
    ○ KubernetesがイメージをPullする際のcredentialをFluxも使用

    ○ Kubernetesとプライベートレジストリの連携については以下参照

    View Slide

  39. GitOpsしてみる
    (※デモではありません)

    View Slide

  40. View Slide

  41. ① Git上のmanifest(YAML)を更新したら自動デプロイ

    View Slide

  42. $ kubectl get pods,services -n flux-test
    NAME READY STATUS RESTARTS AGE
    pod/flux-7bb966d77d-nmt5n 1/1 Running 0 6d
    pod/memcached-6879d9d8d-m4cmt 1/1 Running 1 6d
    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/memcached ClusterIP None 11211/TCP 7d
    A. Fluxセットアップ直後の状態
    ○ Flux以外のリソースはない状態
    ○ Flux監視対象のGitリポジトリ上には、まだ何もYAMLがない状態
    ① Git上のmanifest(YAML)を更新したら自動デプロイ

    View Slide

  43. apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: flux-test
    namespace: flux-test
    spec:
    template:
    metadata:
    labels:
    app: flux-test
    spec:
    containers:
    - image: gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1
    imagePullPolicy: IfNotPresent
    name: flux-test
    ports:
    - containerPort: 9999
    imagePullSecrets:
    - name: regsecret
    apiVersion: v1
    kind: Service
    metadata:
    name: flux-test
    namespace: flux-test
    labels:
    app: flux-test
    spec:
    selector:
    app: flux-test
    ports:
    - port: 80
    targetPort: 9999
    name: http
    apiVersion: v1
    data:
    .dockerconfigjson: eyJJna~略~dmR19
    kind: Secret
    metadata:
    name: regsecret
    namespace: flux-test
    type: kubernetes.io/dockerconfigjson
    ① Git上のmanifest(YAML)を更新したら自動デプロイ
    B. デプロイするYAMLをFluxの監視対象としたGitリポジトリにPush

    View Slide

  44. DONE
    ① Git上のmanifest(YAML)を更新したら自動デプロイ

    View Slide

  45. .
    ..
    ….
    $ kubectl get pods,services -n flux-test
    NAME READY STATUS RESTARTS AGE
    pod/flux-test-5cc6475fbf-nc5f4 1/1 Running 0 16s
    pod/flux-7bb966d77d-nmt5n 1/1 Running 0 6d
    pod/memcached-6879d9d8d-m4cmt 1/1 Running 1 6d
    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/flux-test ClusterIP 10.43.95.187 80/TCP 16s
    service/memcached ClusterIP None 11211/TCP 7d
    ① Git上のmanifest(YAML)を更新したら自動デプロイ
    C. 数分後、FluxによりGit上のYAMLが自動デプロイ
    ○ Git上のYAMLがデプロイされ、新しくPod/Serviceが作成される
    ○ デフォルトのチェック間隔は5分

    View Slide

  46. DONE
    ① Git上のmanifest(YAML)を更新したら自動デプロイ

    View Slide

  47. ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新

    View Slide

  48. A. 監視対象のイメージのタグを更新する
    ○ Flux監視対象のイメージがautomatedになっていること
    ○ image tagを0.0.1 → 0.0.2に更新してコンテナレジストリにPush
    $ fluxctl list-controllers -n flux-test
    CONTROLLER CONTAINER IMAGE RELEASE POLICY
    flux-test:deployment/flux-test flux-test gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1 ready automated
    flux-test:deployment/flux flux quay.io/weaveworks/flux:1.5.0 ready
    flux-test:deployment/memcached memcached memcached:1.4.25 ready
    $ docker build -t gitlab-registry.my.local/endo-k/flux-test/sample:0.0.2 .
    $ docker push gitlab-registry.my.local/endo-k/flux-test/sample:0.0.2
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新

    View Slide

  49. DONE
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新

    View Slide

  50. $ kubectl get pods -n flux-test flux-test-XXXX -o jsonpath='{.spec.containers[:1].image}'
    gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1
    .
    ..
    ….
    $ kubectl get pods -n flux-test flux-test-XXYY -o jsonpath='{.spec.containers[:1].image}'
    gitlab-registry.my.local/endo-k/flux-test/sample:0.0.2
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新
    B. 数分後、Fluxによりレジストリ上の新イメージタグが自動デプロイ
    ○ image tagが0.0.1 → 0.0.2に
    ○ デフォルトのチェック間隔は5分

    View Slide

  51. DONE
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新

    View Slide

  52. $ git diff HEAD^
    diff --git a/flux-test.yaml b/flux-test.yaml
    index 4ef906b..9be39d9 100644
    --- a/flux-test.yaml
    +++ b/flux-test.yaml
    @@ -23,7 +23,7 @@ spec:
    version: v1
    spec:
    containers:
    - - image: gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1
    + - image: gitlab-registry.my.local/endo-k/flux-test/sample:0.0.2
    imagePullPolicy: IfNotPresent
    name: flux-test
    ports:
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新
    C. FluxによりGit上のYAMLも自動更新
    ○ image tagが0.0.1 → 0.0.2に

    View Slide

  53. DONE
    ② 新しいコンテナイメージの自動検出 & 自動デプロイ & Git自動更新

    View Slide

  54. ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    edit

    View Slide

  55. $ kubectl edit deployment flux-test -n flux-test
    .
    ..
    ….
    - image: gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1
    ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    A. GitLab上のYAMLと差分を発生させるため、 kubectl editで直接変更
    ○ image tagを0.0.2 → 0.0.1に

    View Slide

  56. ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    edit
    DONE

    View Slide

  57. ※kubediffをいれてると差分を検知している様子が伺えます
    .
    ..
    ….
    time="2018-09-06T01:05:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:05:27Z" level=info msg="Command exited successfully"
    time="2018-09-06T01:06:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:06:27Z" level=info msg="Command exited successfully"
    time="2018-09-06T01:07:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:07:27Z" level=info msg="Command exited with code: 2"
    time="2018-09-06T01:08:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:08:27Z" level=info msg="Command exited with code: 2"
    ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ

    View Slide

  58. $ kubectl get pods -n flux-test flux-test-XXXX -o jsonpath='{.spec.containers[:1].image}'
    gitlab-registry.my.local/endo-k/flux-test/sample:0.0.1
    .
    ..
    ….
    $ kubectl get pods -n flux-test flux-test-XXYY -o jsonpath='{.spec.containers[:1].image}'
    gitlab-registry.my.local/endo-k/flux-test/sample:0.0.2
    ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    B. 数分後、FluxによりGit上のYAMLが自動デプロイ(Git上との差分解消)
    ○ image tagが0.0.1 → 0.0.2に
    ○ デフォルトのチェック間隔は5分

    View Slide

  59. ※kubediffをいれてると差分が解消した様子が伺えます
    .
    ..
    ….
    time="2018-09-06T01:05:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:05:27Z" level=info msg="Command exited successfully"
    time="2018-09-06T01:06:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:06:27Z" level=info msg="Command exited successfully"
    time="2018-09-06T01:07:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:07:27Z" level=info msg="Command exited with code: 2"
    time="2018-09-06T01:08:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:08:27Z" level=info msg="Command exited with code: 2"
    time="2018-09-06T01:09:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:09:27Z" level=info msg="Command exited with code: 2"
    time="2018-09-06T01:10:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:10:27Z" level=info msg="Command exited with code: 2"
    time="2018-09-06T01:11:26Z" level=info msg="Running '/kubediff' with argments [--namespace=flux-test /data/repo]"
    time="2018-09-06T01:11:27Z" level=info msg="Command exited successfully"
    ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ

    View Slide

  60. ③ Kubernetesの状態と差分があればGit上のmanifest(YAML)を自動デプロイ
    edit
    DONE

    View Slide

  61. Tips

    View Slide

  62. Flux Tips
    ● イメージを自動更新するタグは正規表現などでフィルタ可能

    ● Helmにも対応

    ● Fluxで検知したイベントの Slack通知機能はWeave Cloudのみ
    ○ 代わりにfluxcloudが使えそう
    “Fluxcloud is a valid upstream for Weave, allowing you to send Flux events to Slack or a
    webhook without using Weave Cloud.”

    View Slide

  63. FluxイベントのSlack通知(fluxcloud)

    View Slide

  64. 既知の制限事項
    ● Git上で削除されたリソースの削除はできない
    ○ 現状は個別に kubectl delete する必要がありそう
    he-git-repository>

    ● 1つのfluxデーモンが参照可能なリポジトリは 1つまで
    ○ 1つのfluxデーモンが参照可能なリポジトリは1つまで

    View Slide

  65. 使ってみた感想

    View Slide

  66. やりたいことは概ね実現できそう
    ● manifestファイルをGit管理できる
    ○ 誰がいつ何を変更したのかを把握
    ○ クラスタの移行やクラッシュが発生しても迷わずに再デプロイ可
    ● Git上のmanifestファイルを正としたCI/CD Pipelineが構築できそう
    ○ Weave FluxによりPipeline外部からの更新は取り消せる
    ● Git管理されているmanifestファイルが再デプロイされる
    ● 「差分検知したから同期を一時停止」みたいなワンクッションは欲しい気もする...
    ● オンプレミス上のGitLabとの連携も問題なし

    View Slide

  67. 改善を期待したいところ、注意したいところ
    ● 削除は対応されてほしい
    ○ 滅多にない操作なので当面は不要
    ○ 対応したらしたで、怖さはある
    ○ deleteのときだけkubectlや他のツールを使うか?
    ● たまにしかしないオペレーションは事故りそうでやはり怖い
    ○ issueの進捗を見守る..

    ● 手順通りにテンプレの RBAC適用するとfluxがcluster-admin相当の権限を有するので注意
    ○ typoで他Namespace内にPod作成.. → fluxでは削除できない..( → 管理者に削除依頼)
    ○ Namespace単位で制御したい
    ● Namespace毎にServiceAccount作りRoleBindingでadminを設定
    ● ClusterRolebindingでnamespacesなど必要なリソースに[get list watch]を付与

    View Slide

  68. Jenkins X
    “Jenkins X then automates the management of the
    Environments and the Promotion of new versions of
    Applications between Environments via GitOps.”

    GitOpsを謳っている他のCDツールも試したい
    Argo CD
    “Argo CD is a declarative, GitOps continuous delivery tool
    for Kubernetes.”

    View Slide

  69. まとめ

    View Slide

  70. ● KubernetesのmanifestファイルもGit管理しましょう
    ● GitOpsはGitを正とするCD手法
    ● Weave FluxはKubernetesでGitOpsを実現するツール
    ● GitLabとFluxの連携はとても簡単

    View Slide

  71. ご清聴ありがとうございました

    View Slide