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

Real World .NET Core on Kubernetes

Real World .NET Core on Kubernetes

Mayuki Sawatari

October 18, 2019
Tweet

More Decks by Mayuki Sawatari

Other Decks in Programming

Transcript

  1. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. View Slide

  7. View Slide

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

    View Slide

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

    View Slide

  10. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. kube2iam
    aws-alb-ingress-controller
    external-dns

    View Slide

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

    View Slide

  24. ALB Ingress Controller
    external-dns

    View Slide

  25. 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

    View Slide

  26. 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を書き換えて別環境用に作成

    View Slide

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

    View Slide

  28. 簡単ブランチ開発環境

    View Slide

  29. # 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

    View Slide

  30. # 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

    View Slide

  31. # 完成品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
    ...

    View Slide

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

    View Slide

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

    View Slide

  34. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. Approval (承認) 機能

    View Slide

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

    View Slide

  43. View Slide

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

    View Slide

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

    View Slide

  46. 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"}

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. View Slide

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

    View Slide

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

    View Slide

  55. 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

    View Slide

  56. 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

    View Slide

  57. 起動時に設定をログに出力しておく
    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"
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  60. 自身の名前や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

    View Slide

  61. 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

    View Slide

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

    View Slide

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

    View Slide

  64. 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",
    });

    View Slide

  65. 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(); // ヨシ!
    }
    }

    View Slide

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

    View Slide

  67. View Slide

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

    View Slide

  69. 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

    View Slide

  70. View Slide

  71. View Slide