Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

(ハンドルネーム) https://github.com/mayuki

Slide 3

Slide 3 text

Kubernetes環境の構築と.NET Coreアプリを 乗せる際のポイントと実際に使用しての知見

Slide 4

Slide 4 text

サービスと構成 Kubernetes / Amazon EKS の選択について 基本方針 Kubernetes環境構築あれこれ CIとデプロイ ロギング .NET Core にまつわるいろいろ

Slide 5

Slide 5 text

Lifebear 手帳アプリ 2019年4月にiOS版リニューアル

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Webアプリケーション APIサーバー (新, 旧) Worker バッチ

Slide 9

Slide 9 text

Amazon EKS (Kuberentes) Amazon EC2 (Linux / EKS Optimized AMI) Application Load Balancer Aurora, ElastiCache (Redis), S3 など

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

難しいことをやりすぎない できるだけシンプルにする クラスターに状態は持たない

Slide 12

Slide 12 text

Kubernetesというエコシステム Amazon ECSの概念とKubernetesの概念 2018年末に東京リージョンでEKSがGA

Slide 13

Slide 13 text

AWSのサービスとの親和性、サポート Kubernetesやインフラに興味がない

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

YAMLと付き合う クラスターとnamespace構成 利用している関連ツール リソースプランニング

Slide 16

Slide 16 text

たくさんのYAMLと環境ごとの差分 共通化しつつ差分を扱えるツールの必要性 Helm Kustomize

Slide 17

Slide 17 text

Overlay という概念で複数環境を扱える 出力結果はあくまでコネコネしたYAML

Slide 18

Slide 18 text

引用: github.com/kubernetes-sig/kustomize

Slide 19

Slide 19 text

引用: github.com/kubernetes-sig/kustomize

Slide 20

Slide 20 text

スタンダードになりそう 追加知識が多くない 一括で共通の値を設定できたり便利

Slide 21

Slide 21 text

クラスターはアプリ+環境単位 namespaceはメイン環境+ブランチ等のサブ環境

Slide 22

Slide 22 text

kube2iam aws-alb-ingress-controller external-dns

Slide 23

Slide 23 text

PodにIAMロールをアタッチできる拡張 稼働するアプリ単位で権限コントロール apiVersion: apps/v1 kind: Deployment metadata: name: my-app annotations: iam.amazonaws.com/role: EksPodRole-MyApplication 参考: サービスアカウントの IAM ロール

Slide 24

Slide 24 text

ALB Ingress Controller external-dns

Slide 25

Slide 25 text

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app annotations: kubernetes.io/ingress.class: alb 略: ... # ロードバランサーに割り当てる DNS 名 external-dns.alpha.kubernetes.io/hostname: api.dev.example.com spec: rules: - http: paths: - path: /SubSystem/* backend: serviceName: my-api-subsystem servicePort: 80 - path: /* backend: serviceName: my-api servicePort: 80

Slide 26

Slide 26 text

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app annotations: kubernetes.io/ingress.class: alb 略: ... # ロードバランサーに割り当てる DNS 名 external-dns.alpha.kubernetes.io/hostname: api-branch-feature-1.dev.example.com spec: rules: - http: ... hostnameを書き換えて別環境用に作成

Slide 27

Slide 27 text

専用のゾーンを切っておく

Slide 28

Slide 28 text

簡単ブランチ開発環境

Slide 29

Slide 29 text

# base/deployment.yaml apiVersion: apps/v1 kind: Deployment ... # base/ingress.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app annotations: # ロードバランサーに割り当てる DNS 名 external-dns.alpha.kubernetes.io/hostname: dev.example.com ... # base/kustomization.yaml resources: - deployment.yaml - ingress.yaml

Slide 30

Slide 30 text

# overlay/branch/kustomization.yaml namespace: dev-branch-feature-1 commonAnnotations: k8s.example.com/gitBranch: dev-branch-feature-1 bases: - ../../base/ patches: - ingress-patch.yaml # overlay/branch/ingress-patch.yaml - op: replace path: /metadata/annotations/external-dns.alpha.kubernetes.io~1hostname value: dev-branch-feature-1.example.com

Slide 31

Slide 31 text

# 完成品YAML namespace: dev-branch-feature-1 apiVersion: apps/v1 kind: Deployment metadata: annotations: k8s.example.com/gitBranch: dev-branch-feature-1 ... --- namespace: dev-branch-feature-1 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app annotations: k8s.example.com/gitBranch: dev-branch-feature-1 external-dns.alpha.kubernetes.io/hostname: dev-branch-feature-1.example.com ...

Slide 32

Slide 32 text

Podに対するCPUとメモリの割り当て量 CPUの割り当ては1コア時間以下が基本 https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits

Slide 33

Slide 33 text

カツカツにしすぎるとデプロイ時の余剰がない CoreDNSとかアプリ以外の分 Kubeletに割り当てる分

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

CIとデプロイにはAzure Pipelinesを利用 Releaseパイプライン Self-host agent

Slide 36

Slide 36 text

Spinnaker: Continuous Deployment Prow: Kubernetes based CI/CD AWS CodeBuild/CodePipeline

Slide 37

Slide 37 text

Dockerビルドする ECRにDockerイメージをpushする Kubernetes用YAMLを生成、成果物として保存 生成したYAMLを適用する (開発環境デプロイ)

Slide 38

Slide 38 text

Release パイプラインを使用 1. 成果物にある生成されたYAMLをダウンロード 2. annotation内のリリース番号をsedで雑置換 3. kubectl apply -f manifest.prod.yaml 4. kubectl rollout status で待機

Slide 39

Slide 39 text

やっている手順はそれだけ

Slide 40

Slide 40 text

リソースにラベルを振って個々でapply apiVersion: apps/v1 kind: Deployment metadata: labels: app: myapp k8s.example.com/type: deployment kubectlでラベルを指定し特定のリソースだけ反映

Slide 41

Slide 41 text

Approval (承認) 機能

Slide 42

Slide 42 text

ビルドはDockerビルド+YAMLを生成 デプロイはkubectl apply フレーバーはかけてあるけど仕組みは単純にした

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

ログについて考えると ログを出力する側 ログを集めて取り扱う側

Slide 45

Slide 45 text

一般的なセオリー通り出力は標準出力へ 構造化ロギング JSONで吐き出す

Slide 46

Slide 46 text

Microsoft.Extensions.Loggingのログプロバイダー https://github.com/mayuki/JsonStreamLogger 1 ログレコード、1 JSON行としてStreamに出力 logger.Log(LogLevel.Information, "[{Id}] is {Hello}", 12345, "Konnnichiwa"); logger.Log(LogLevel.Warning, new EventId(987, "NanikaEvent"), "[{Id}] is {Hello}", 67890, "Nya-n"); logger.LogError(ex, "[{Id}] is {ExceptionType}: {ExceptionMessage}", 77777, ex.GetType().FullName, ex.Message); {"Category":"Test","LogLevel":2,"EventId":{"Id":0,"Name":null},"State":{"Id":12345,"Hello":"Konnnichiwa"},"Exception":null,"Message" :"[12345] is Konnnichiwa"} {"Category":"Test","LogLevel":3,"EventId":{"Id":987,"Name":"NanikaEvent"},"State":{"Id":67890,"Hello":"Nya- n"},"Exception":null,"Message":"[67890] is Nya-n"} {"Category":"Test","LogLevel":4,"EventId":{"Id":0,"Name":null},"State":{"Id":77777,"ExceptionType":"System.Exception","ExceptionMess age":"Yabai"},"Exception":{"Name":"System.Exception","Message":"Yabai","StackTrace":"(snip)","InnerException":null}},"Message":"[777 77] is System.Exception: Yabai"}

Slide 47

Slide 47 text

アプリが実際に使うのはILogger をラップしたもの

Slide 48

Slide 48 text

ロガーはすべて登録しておいて設定で無効化 - Logging__Console__LogLevel__Default: None - Logging__JsonStream__LogLevel__Default: Trace { "Logging": { "Console": { "LogLevel": { "Default": "Debug“ } }, "JsonStream": { "LogLevel": { "Default": "None“ } } } }

Slide 49

Slide 49 text

Kubernetesとコンテナーのログ Datadog Log Management

Slide 50

Slide 50 text

JSONログの取り扱い 高度なタグベースでの検索 お値段には注意

Slide 51

Slide 51 text

セオリーに従う 構造化ロギング Datadog Log Management便利

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

コンテナー化 .NET Coreアプリの設定 .NET Coreでワーカーアプリを作る Pod内からPodの関連情報を取得する システムリソースの枯渇 Readiness/Liveness Probe

Slide 54

Slide 54 text

ローカルファイルシステムに極力頼らない 起動する場所や仕方は様々、設定で柔軟に ログを吐く Windowsの挙動に依存しない

Slide 55

Slide 55 text

appsettings.jsonとConfigMap apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: api envFrom: - configMapRef: name: aspnetcore-environment-variables volumeMounts: - name: app-secrets mountPath: /app/secrets readOnly: true apiVersion: v1 kind: ConfigMap metadata: name: aspnetcore-environment-variables data: # 開発環境 NETCORE_ENVIRONMENT: Development ASPNETCORE_ENVIRONMENT: Development

Slide 56

Slide 56 text

appsettings.jsonとSecret public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostContext, configure) => { configure // k8s 上では /app/secrets にシークレットをマウントするお気持ち .AddJsonFile($"secrets/appsettings.json", optional: true); }) apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: api volumeMounts: - name: app-secrets mountPath: /app/secrets readOnly: true volumes: - name: lifebear-app-secrets secret: secretName: app-secrets optional: true

Slide 57

Slide 57 text

起動時に設定をログに出力しておく https://gist.github.com/mayuki/0d84d3d0b2bf8f089f69b042f4b650b8 ConnectionStrings:Default: server=Server;user id=User;password=;database=MyDatabase MyApp:Hello: Konnichiwa! MyApp:Nantoka:Kantoka: True Application started. Press Ctrl+C to shut down. Hosting environment: Production { "MyApp": { "Hello": "Konnichiwa!", "Nantoka": { "Kantoka": true } }, "ConnectionStrings": { "Default": "server=Server;user id=User;password=NankaPassword;database=MyDatabase" } }

Slide 58

Slide 58 text

ジョブキューワーカーのようなものを作る MicroBatchFramework https://github.com/Cysharp/MicroBatchFramework

Slide 59

Slide 59 text

ロギングの口が提供されている ライフサイクルを扱える apiVersion: apps/v1 kind: Deployment spec: template: spec: # シャットダウン要求を受けてからアプリが kill されるまでの猶予時間(秒) terminationGracePeriodSeconds: 60

Slide 60

Slide 60 text

自身の名前やannotationなどを取得したいケース Downward API Kubernetes API Pripod apiVersion: v1 kind: Pod spec: containers: - name: test-container env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace

Slide 61

Slide 61 text

Podの情報を簡単に取得できるライブラリ https://github.com/mayuki/Pripod ラベルやアノテーションも取得できる Console.WriteLine($"Pod: {Pod.Current.Namespace}/{Pod.Current.Name} @ {Pod.Current.NodeName}"); Console.WriteLine("Labels:"); foreach (var keyValue in Pod.Current.Labels) { Console.WriteLine($" - {keyValue.Key}: {keyValue.Value}"); } IsRunningOnKubernetes: True Pod: default/consoleapp1-595b95b5f7-xsdjc @ docker-for-desktop Labels: - pod-template-hash: 1516516193 - run: consoleapp1 Deployment: default/consoleapp1

Slide 62

Slide 62 text

雑に一台のサーバーにPodを起動しまくると リソースが枯渇することがある https://github.com/dotnet/corefx/issues/32024

Slide 63

Slide 63 text

Kubernetesのヘルスチェック機構に対応させる Readiness Probe: アプリが準備できているか Liveness Probe: アプリが正常に動作しているか

Slide 64

Slide 64 text

ASP .NET CoreにはHealth Check APIがある using Microsoft.Extensions.Diagnostics.HealthChecks; services.AddHealthChecks() .AddCheck("Readiness", () => HealthCheckResult.Healthy()) .AddCheck("Liveness"); ... app.UseHealthChecks("/health/readiness", new HealthCheckOptions { Predicate = check => check.Name == "Readiness", }); app.UseHealthChecks("/health/liveness", new HealthCheckOptions { Predicate = check => check.Name == "Liveness", });

Slide 65

Slide 65 text

ASP .NET CoreにはHealth Check APIがある public class ApiHealthCheck : IHealthCheck { public async Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default ) { // 外部サービスへの接続性をチェックする var service = NantokaServiceClient.Instance; await service.GetNantokaKantokaAsync(); return HealthCheckResult.Healthy(); // ヨシ! } }

Slide 66

Slide 66 text

コンテナー化されると若干事情が変わる 基本は大きく変わらない

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

無理ない範囲でやっていき Kubernetesはインフラをアプリに寄せる コンテナー前提な.NET Coreアプリを作る

Slide 69

Slide 69 text

Kubernetes AWS/EKS ベストプラクティス 2019.2 https://speakerdeck.com/mumoshu/eksbesutopurakuteisu2019-dot-2-number-jawsdays Kubernetes Failure Stories https://srcco.de/posts/kubernetes-failure-stories.html Kubernetes上でアプリケーションを運用するまで の道のり https://speakerdeck.com/shmurata/the-way-to-run-applications-with-kubernetes

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

No content