耐障害性向上・パフォーマンス改善・運用負荷軽減をどう実現する?事業を支えるSREのノウハウを共有 に行ってきた #SREノウハウ

耐障害性向上・パフォーマンス改善・運用負荷軽減をどう実現する?事業を支えるSREのノウハウを共有 に参加しました。オンライン参加。簡単に所感をまとめます。

enechange-meetup.connpass.com

所感

「コスト最適化」「安定稼働」「アジリティ向上」 のための取り組みが聴けてよかったです。知らない単語とか出てきてちょっと話に追い付けないところもあったけど。

正直、昔は SRE = インフラエンジニア みたいな雑なイメージしか持ってなかったけど、ここ何年か SRE チームと一緒に仕事する機会が増えてからは自分にも「コスト最適化」「安定稼働」「アジリティ向上」の意識が芽生えるようになりました。たぶん。

SRE に限った話でもないけど、現状をしっかり把握して仮説を立てて検証するということと、ものごとを定量的に把握することが重要な気がします。

自分専用の使い捨て検証環境欲しい...。

以下、メモから抜粋。

アソビューSREで実施した各種最適化の視点について

  • 性能限界を意識する
    • Aurora
      • 24xlarge
      • 最大コネクション数 16000
  • キャパシティプランニング
  • 負荷テストで指標を定める
    • どれくらいの負荷に耐えられるか
    • どれくらいパフォーマンスが出せるか
  • スペック/パフォーマンスの相関
    • 限界値に対して必要なスペック/リソース
  • Pod 負荷率
    • Pod ごとの性能限界
    • 秒間処理可能数
  • スパイクを予測する
  • 安定稼働基準
    • 5倍のスパイクに耐えられるように
      • Pod 負荷率, CPU 利用率 20%以内
  • Aurora Serverless v2
    • 利用メモリが減る (CPU に依存する)
  • HPA
    • datadog メトリクス
  • CircleCI
    • ジョブが実行されるリージョンに近いところにビルドイメージを配置する
    • オーバーヘッド削減
    • 通信コスト削減

SRE初心者が急成長しているEV充電サービスの安定稼働にコミットした話

  • OCPP
  • リソース不足による障害
    • スケールアウト
    • バッチ実行頻度を下げる
    • DB スケールアップ
  • デプロイの際にCPU利用率が跳ねる
    • 旧コンテナが落ちる際に一斉に接続断される
    • WebSocket 再接続による負荷増
  • 再接続が集中しない仕組み
  • Exponential Backoff
    • リトライによる再接続のタイミングを分散させる
  • 線形デプロイ (CodeDeploy)
    • トラフィックを徐々に Blue → Green に置き換える
      • WebSocket との相性イマイチ
      • トラフィック置き換えは新規接続のみが対象
      • 接続済みはそのまま残る
  • Graceful Update (自前)
    • CodeDeploy → EventBridge に通知
    • Lambda で接続断
    • 少しずつ置き換える

2023年度版!Chatwork流Kubernetesの運用方法

  • SRE に求められるもの
    • コスト最適化
    • 安定稼働
    • アジリティ向上
  • EC2 台数増加でコスト増
  • スケールアウトする際に EC2 起動が間に合わず不安定に
    • balloon
    • 余剰ノードを確保するための仕組み
  • Karpenter
  • ArgoCD
  • 開発者が開発しやすい環境
    • SRE チームがボトルネックにならないように
    • 本番アカウント/検証アカウント分離
      • 開発者が検証アカウントで作業できるように
    • 自分専用の使い捨て検証環境
  • Terraform x Atlantis

cloudfront functions でクエリパラメータを追加したい

微妙にハマったのでメモ。

docs.aws.amazon.com

関数の中ではクエリパラメータなどはオブジェクトとして扱うことになる。例えば、クエリパラメータに foo=bar を追加する場合は次のようにする。

function handler(event) {
    var request = event.request;

    request.querystring['foo'] = { value: 'bar' };

    return request;
}

現場からは以上です。

Container probes と Spring Boot event listeners

備忘録。Kubernetes にデプロイしている Spring Boot アプリケーションにおいて、とある初期化処理を Spring の起動プロセスの中で実行したい。どのタイミングで実行するのがいいかを調べたときのドキュメントリンク集。

結局、冒頭の初期化処理は ApplicationStartedEvent をハンドリングして実行するようにした。おそらく、Liveness Probe や Readiness Probe が始まる前。

Terraform活用大全 - IaCの今。Lunch LT に行ってきた #Terraform_findy

Terraform活用大全 - IaCの今。Lunch LT に参加しました。オンライン参加。簡単に所感をまとめます。

findy.connpass.com

所感

terraform test は気になるところだけど、Terraform の理解というか経験がまだまだ浅くて、宣言的に定義できるコードにテストを書くメリットがあまりイメージできていない...。

tfstate とかディレクトリをどう分割するかみたいなところはたぶん正解はなくて、それぞれの環境や組織にマッチしたルールを決めて運用する必要がありそう。そこが難しいところなんだろうけど。Terraform のベストプラクティスがあるから見てみるか。

www.terraform-best-practices.com

最後のセッションの、独自の module を使わない利点どんなのがあるのかと思ったけど話を聞いてなるほど過ぎた。これも環境や組織に合わせたルールだよね。

以下、メモから抜粋。

Terraform に test コマンドがやってきた

www.docswell.com

  • import ブロックの id に expression 指定できるように
  • .tftest.hcl
  • command
    • plan or apply
    • 作成したリソースはテスト終了補に削除される
  • テストファイルはプロダクションコードと分離できる
  • Custom Conditions
    • input variable validation
    • 入力値の確認を terraform plan で確認しなくてもよくなった
  • output の値を検証したり

Terraform Registryで公開されているTerraform Modulesが便利だった件

  • 抽象化できずリソース数が多くなる
  • サブネット
    • CIDRの差分が面倒
  • AWS CDK
  • Terraform Registry
    • 抽象化
    • 少ない記述量
  • 信頼性
    • 公式プロバイダ
    • ダウンロード数
    • アップデート頻度

tfstateの分割パターンとディレクトリー構成への適用

  • Terraform 作業者が多いと tfstate への操作が衝突する
    • target 指定
  • tfstate 分割
  • 依存度が低いリソース同士は別 tfstate で管理しやすい
  • 分割パターン
    • プロバイダ別
    • 実行環境別

Terraformでmoduleを使わずに複数環境を構築して感じた利点

  • 内製のモジュールを使わない
  • backend と tfvars で切り替える
    • .tfbackend
    • .tfvars
  • 同一ディレクトリ内の同一 tf を複数環境に適用する
    • terraform init -backend-config=dev.tfbackend
    • terraform plan -var-file=dev.tfvars
    • ↑のラッパーを用意しておく
  • workspace
  • Terraformでmoduleを使わずに複数環境を構築する
  • module にまつわる要素
    • module の粒度
    • variables の命名規則や用途
    • variables の型に object を使っているか
    • module 間の呼び出しは許容しているか
    • ...
  • キャッチアップしやすいコード
  • 独自の設計を考慮しなくていい
    • module の設計は難易度が高い
    • 保守性とか

データベース移行のウラガワ − 円滑なリリースのために取り組んだLT に行ってきた #データベース_findy

データベース移行のウラガワ − 円滑なリリースのために取り組んだLT に参加しました。オンライン参加。簡単に所感をまとめます。

findy.connpass.com

所感

やはり、まずはサービス停止が必要かどうかを最初に検討するのが重要そう。あとは、移行前後のデータ不整合リスクをいかに排除するか。

移行作業を2段階に分けたり、書き込みだけ止めて読み込みは生かすことで、サービスの全停止を避ける工夫をしてたのはなるほどでした。

DB移行に限った話ではないけど、他社事例をそのまま参考にするとかは基本的にはできなくて、自社のシステムや業務を正確に把握して自分たちにとって最適な移行計画を立てられるかが重要だと思いました。どういう事業をやっているか次第なところもあるし。

以下、メモから抜粋。(資料公開されたら追記する。あと、最後の方が聴けなかったので録画が公開されたら観る。)

データベースをMySQL8.0へ移行を実行する上で考えてたこと

  • 移行前後の仕様変更
    • SQL構文
    • 組み込み関数
    • パフォーマンス
  • サービス停止
    • 許容されるダウンタイム
  • 未コミットのデータがあるとバージョンアップに時間がかかる
  • 旧新バージョンで実行クエリを記録/再生

データベースの移行方式を検討した話

  • 基幹DBリプレイス
  • SQL Server
    • フロント
    • バックエンド
    • レポート
    • バッチ
    • readonly
    • dms
  • サービス停止有無
  • オンライン移行
    • 接続切り替えが瞬時にできるか
    • データロスト/不整合がないか
  • ダブルライト
    • アクセス箇所が膨大で断念
  • サービス停止時間
    • 新旧でデータを一致させる時間 (データ同期)
  • export/import
    • 時間がかかる
  • バックアップ/リストア
  • レプリケーションによるリアルタイム同期
  • 数日前からフルリストア開始
  • 2段階リリース
    • 2グループずつリプレイス
    • サービス停止時間短縮

Aurora MySQL v1からv3へ一段飛ばしのバージョンアップをした話

www.docswell.com

  • サービス停止
    • データ不整合リスクを避ける
  • 暗黙のソート順が不定
  • DMS レプリケーション
  • MySQL Connector/J
  • DBの使い方次第でハマるポイントは変わる
    • 暗黙のソート順
    • デフォルト Collation
    • TempTable エンジン
    • 接続ライブラリ
    • レプリケーショントラブル
    • 性能/サイジング
  • 他社事例は参考程度に

サービスへの影響を抑えてデータベースの移行を実施したはなし

  • Aurora → Cloud SQL
  • Cloud SQL
    • BigQuery 連携
    • マルチリージョン
  • AWS VPCVPNGoogle CLoud VPC
  • データ整合性
  • Cloud SQL にレプリカ構築 (reader)
  • 切り替え中は書き込み停止
    • 読み込みはそのまま
    • 全サービス切り替え完了するまで書き込みさせない
  • 読み取り/書き込みそれぞれにドメインを割り当てる
    • DNSで書き換え
    • コネクションプールの生存期間に注意

RDB無停止移行への挑戦

www.docswell.com

  • ディスプレイ広告入稿/配信システム
  • Oracle
  • サービス停止できない
    • 広告配信の課金ができない
    • 課金=更新処理
      • 読み取り専用にもできない
  • 並行書き込み機能
    • 非機能要件のため2相コミットはしない
    • 新旧DB用の Repository を DI
    • 旧→新の順で更新する
    • 新が更新失敗しても結果を返す
  • 新旧差分確認 API
    • 1レコードずつ差分を確認可
    • SELECT FOR UPDATE でロック可
  • 新旧差分マイグレ API
    • 1レコードずつ旧→新に反映する

Cloud Spanner で ROW_NUMBER の代わりに連番を出力したい

Cloud Spanner でクエリの結果に連番を振ろうと思ったら ROW_NUMBER 関数がなかったので代わりの方法を探しました。(似たような関数も見つからなかった)

cloud.google.com

サンプルデータ

ChatGPT に作ってもらったサンプルデータ。

-- DDL
CREATE TABLE Singers (
    SingerId   STRING(36) NOT NULL,
    FirstName  STRING(1024),
    LastName   STRING(1024),
    BirthDate  DATE
) PRIMARY KEY (SingerId);

-- DML
INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate) VALUES
('f47ac10b-58cc-4372-a567-0e02b2c3d479', 'John', 'Lennon', '1940-10-09'),
('7e57d004-2b97-0e7a-8b1e-4946d34c0ae4', 'Paul', 'McCartney', '1942-06-18'),
('2403d2c7-2277-4faf-908a-0798f91e0908', 'George', 'Harrison', '1943-02-25'),
('3f8c7d18-a6cc-4855-955c-3838387d09d8', 'Ringo', 'Starr', '1940-07-07'),
('e17d09a8-4c21-4eff-be52-2634681c3d7d', 'Mick', 'Jagger', '1943-07-26'),
('624615d9-5c3d-4aee-a9f8-213c5f577ab9', 'Keith', 'Richards', '1943-12-18'),
('0ae8db3a-a4e2-43df-9b64-b0d8e7c4f3db', 'Robert', 'Plant', '1948-08-20'),
('e1f095d7-1ce3-40c0-a6aa-743278862d4c', 'Jimmy', 'Page', '1944-01-09'),
('52c9e469-2886-491f-a6ea-e841e1625b04', 'Roger', 'Daltrey', '1944-03-01'),
('a8b3c0c9-3f90-4bdf-8f04-d078f3a4f9a5', 'Pete', 'Townshend', '1945-05-19');

連番を出力する

例えば、誕生日で並び替えた結果に連番を出力する場合。

SELECT
    (SELECT count(*) + 1 FROM Singers Singers2 WHERE Singers.BirthDate > Singers2.BirthDate),
    *
FROM Singers ORDER BY BirthDate;

結果:

|   |SingerId                            |FirstName|LastName |BirthDate |
|---|------------------------------------|---------|---------|----------|
|1  |3f8c7d18-a6cc-4855-955c-3838387d09d8|Ringo    |Starr    |1940-07-07|
|2  |f47ac10b-58cc-4372-a567-0e02b2c3d479|John     |Lennon   |1940-10-09|
|3  |7e57d004-2b97-0e7a-8b1e-4946d34c0ae4|Paul     |McCartney|1942-06-18|
|4  |2403d2c7-2277-4faf-908a-0798f91e0908|George   |Harrison |1943-02-25|
|5  |e17d09a8-4c21-4eff-be52-2634681c3d7d|Mick     |Jagger   |1943-07-26|
|6  |624615d9-5c3d-4aee-a9f8-213c5f577ab9|Keith    |Richards |1943-12-18|
|7  |e1f095d7-1ce3-40c0-a6aa-743278862d4c|Jimmy    |Page     |1944-01-09|
|8  |52c9e469-2886-491f-a6ea-e841e1625b04|Roger    |Daltrey  |1944-03-01|
|9  |a8b3c0c9-3f90-4bdf-8f04-d078f3a4f9a5|Pete     |Townshend|1945-05-19|
|10 |0ae8db3a-a4e2-43df-9b64-b0d8e7c4f3db|Robert   |Plant    |1948-08-20|

ついでに、出力した連番を使って任意の ID を付けたい。

SELECT
    FORMAT('BirthDate-%02d', (SELECT count(*) + 1 FROM Singers Singers2 WHERE Singers.BirthDate > Singers2.BirthDate)),
    *
FROM Singers ORDER BY BirthDate;

結果:

|            |SingerId                            |FirstName|LastName |BirthDate |
|------------|------------------------------------|---------|---------|----------|
|BirthDate-01|3f8c7d18-a6cc-4855-955c-3838387d09d8|Ringo    |Starr    |1940-07-07|
|BirthDate-02|f47ac10b-58cc-4372-a567-0e02b2c3d479|John     |Lennon   |1940-10-09|
|BirthDate-03|7e57d004-2b97-0e7a-8b1e-4946d34c0ae4|Paul     |McCartney|1942-06-18|
|BirthDate-04|2403d2c7-2277-4faf-908a-0798f91e0908|George   |Harrison |1943-02-25|
|BirthDate-05|e17d09a8-4c21-4eff-be52-2634681c3d7d|Mick     |Jagger   |1943-07-26|
|BirthDate-06|624615d9-5c3d-4aee-a9f8-213c5f577ab9|Keith    |Richards |1943-12-18|
|BirthDate-07|e1f095d7-1ce3-40c0-a6aa-743278862d4c|Jimmy    |Page     |1944-01-09|
|BirthDate-08|52c9e469-2886-491f-a6ea-e841e1625b04|Roger    |Daltrey  |1944-03-01|
|BirthDate-09|a8b3c0c9-3f90-4bdf-8f04-d078f3a4f9a5|Pete     |Townshend|1945-05-19|
|BirthDate-10|0ae8db3a-a4e2-43df-9b64-b0d8e7c4f3db|Robert   |Plant    |1948-08-20|

現場からは以上です。

count や dynamic + for_each で resource や要素の生成を切り替えたい

備忘録。環境などの条件によって Terraform の resource 生成を切り替えたい。たまに使う。

例) cloudfront functions の resource 生成 & ビヘイビアへの関連付け

resource 自体の生成は count で切り替える。variables にフラグを用意して呼び出し元で切り替えを宣言できるようにする。なるほど。

resource "aws_cloudfront_function" "example" {
  count   = var.enable_function ? 1 : 0
  name    = "example"
  runtime = "cloudfront-js-1.0"
  comment = "example function"
  publish = true
  code    = file("${path.module}/example.js")
}

resource 内の要素の生成は dynamicfor_each で切り替える。

resource "aws_cloudfront_distribution" "distribution" {
  origin {
    ...
  }
  default_cache_behavior {
    ...
    dynamic "function_association" {
      for_each = var.enable_function ? ["1"] : []
      content {
        event_type   = "viewer-request"
        function_arn = aws_cloudfront_function.example[0].arn
      }
    }
  }
}

さらにこう書くと resource の存否で分岐できる。(学び)

resource "aws_cloudfront_distribution" "distribution" {
  origin {
    ...
  }
  default_cache_behavior {
    ...
    dynamic "function_association" {
      for_each = aws_cloudfront_function.example
      content {
        event_type   = "viewer-request"
        function_arn = function_association.value.arn
      }
    }
  }
}

現場からは以上です。