every Tech Blog

株式会社エブリーのTech Blogです。

メンテナンスで出来るだけ再現性を持たせて考えることを減らそう

はじめに

こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。

今回はバックエンド系を触っている人なら誰しもが一度は関わるであろうメンテナンス(以下メンテと省略します)の話をしていきたいと思います、

実際のメンテのための計画方法は以下の記事で紹介されていますので、気になる方はご覧ください。

tech.every.tv

そもそもなぜメンテナンスが必要になってくるのか

まずメンテが起きないように基本オンタイムでアプリケーションが更新されるように仕組みを作ることは大切です。
たとえば、CI/CD からブルーグリーンデプロイメントなどの手法を使って、アプリケーションの更新を自動化することで、サービス利用者にとっては透過的にアプリケーションが更新されることができます。

しかしアプリケーションが動いているインフラ周りの変更や、アプリケーションの大規模な仕様変更などが発生すると、どうしてもダウンタイムを伴うメンテナンスが必要になります。

メンテの手順にどうやって再現性を持たせるか

サービスを止めるにあたって、メンテの手順を明確にしておくことが大切です。
しかしその手順は人間が行うものであるため、ヒューマンエラーが発生する可能性があります。

そのため、メンテ時には、アプリケーションの状態を元に戻すための手順を明確にしておくことが大切です。

例えば AWS の RDS でメンテンスを行う場合でも以下のような手順を踏みます。
(今回は例でインプレースアップグレードで話をします)

  1. バッチが走らないようにする
  2. アラートを一時的に無効にする
  3. サービスの停止を行う(ロードバランサーの切り替えて固定レスポンスを返す等)
  4. DB のバックアップを取る
  5. DB のアップデートを適用する
  6. アップデート後に動作確認を行う
  7. サービスを再開する(ロードバランサーの切り替えを元に戻す)
  8. アラートを有効にする
  9. バッチを再開する
  10. 必要であればバッチの再実行を行う

※実際に mysql8 化をした記事があるので気になる方はご覧ください。

tech.every.tv

これらの手順だけでもかなりの数があり、毎回コンソール上から手動で実行するのはミスが発生しやすいです。
意図せぬ設定をしてどこで間違ったのかがわからないということが起こりがちです。

そのため、メンテ時には、アプリケーションの状態を元に戻すための手順を自動化出来るようにしておくことが大切です。

コード化して再現性を持たせる

例えば、Terraform を使って、インフラの状態をコードで管理することで、メンテ時にはコードを実行するだけでアプリケーションの状態を元に戻すことができます。
以下はロードバランサーのルールを変更するコードの例です。

こちらでは、ロードバランサーのリスナールールを変更して、メンテ中には固定レスポンスを返すようにしています。
メンテ解除時は変更差分を取り消して apply するだけで元に戻すことができます。

// メンテ用のルール
resource "aws_alb_listener_rule" "server_maintenance_rule" {
  listener_arn = aws_alb_listener.lb_https.arn
  priority     = 4 // priorityが競合しないように番号を調整しておく

  action {
    type             = "fixed-response"

    fixed_response {
      content_type = "application/json"
        status_code  = "503"
        message_body = jsonencode(
        {
          message = "メンテナンス中です"
        }
      )
    }
  }
  condition {
      host_header {
        values = ["server.example.com"]
      }
  }
}

// 通常のルール1
resource "aws_alb_listener_rule" "server_rule" {
  listener_arn = aws_alb_listener.lb_https.arn
  priority     = 5 // 通常: 1

  action {
    type             = "forward"
    target_group_arn = var.server_tg_arn
  }

  condition {
    host_header {
      values = ["server.example.com"]
    }
  }
}

// 通常のルール2
resource "aws_alb_listener_rule" "dashboard_rule" {
  listener_arn = aws_alb_listener.lb_https.arn
  priority     = 6 // 通常: 2

  action {
    type             = "forward"
    target_group_arn = var.dashboard_tg_arn
  }

  condition {
    host_header {
      values = ["dashboard.example.com"]
    }
  }
}

// 通常のルール3
resource "aws_alb_listener_rule" "web_rule" {
  listener_arn = aws_alb_listener.lb_https.arn
  priority     = 7 // 通常: 3

  action {
    type             = "forward"
    target_group_arn = var.web_tg_arn
  }

  condition {
    host_header {
      values = ["example.com"]
    }
  }
}

コマンドライン上で再現性を持たせる

また物によってはコード管理していないものもあると思います。
そこでは AWS CLI で事前にコマンドを作成しておくことで、メンテ時にはコマンドを実行するだけで適用することができます。

これは AWS CLI で RDS のインスタンスのエンジンバージョンを変更するコマンドの例です。
本来であればコンソール上でボタンを押して切り替えたり手で打つところがなくなるので、ヒューマンエラーを減らすことができます。

aws rds modify-db-instance \
    --db-instance-identifier example-rds \
    --apply-immediately \
    --engine-version 8.0.35 \
    --option-group-name default:mysql-8-0 \
    --db-parameter-group-name mysql80-example-rds \
    --allow-major-version-upgrade \
    --enable-performance-insights

事前に模擬メンテを行う

実際に手順書を作成していざ本番だと、コマンド上のミスや考慮漏れなどが発生することがあります。
そのため可能であれば、開発環境や QA 環境等で事前に模擬でメンテを行うことで、本番でのミスを減らすことができます。

ただし本番とは異なる環境で行うため、実際のメンテ時には環境の違いを考慮して手順を確認することが大切です。
(実際にハマったものですが、DB アップデート時にインスタンスサイズの違いで開発環境では 15 分程度だったのものが本番では 30 分程度かかりました)

そのため出来ることなら本番と近い環境で行うことが望ましいです。

アプリケーションコードの変更なしでメンテに追従できるようにする

こちらは再現性の観点からは外れますが、アプリケーションコードの変更なしでメンテに追従すると、サービスへのデプロイをすることなく切り替えが瞬時に行えるようになります。

例えば API 通信をしているアプリケーション(クライアントアプリや web、dashboard など)で事前にメンテ用に特定レスポンス(503)の挙動を考慮しておくことで、メンテ時にはアプリケーションコードの変更なしでメンテに追従することができます。

const response = await fetch("https://api.example.com");
if (response.status === 503) {
  // メンテナンス中の場合の処理
  // リダイレクトなり、画面に描画したり
  return;
}

// 移行通常の処理

まとめ

ここまでメンテに関わってくることはそれなりにありましたが、メンテナンスはサービスを維持するために必要な作業です。
再現性を持たせたり手順を自動化することで、ヒューマンエラーを減らすことができ精神的にもとても楽になります。

完璧にすることは難しいですが、今後ともトラブルなくメンテをやりきれるよう普段から意識してサービス開発に取り組んでいきたいと思います。