Serverless Frameworkのロールバックコマンドを使用してみた

2023.04.28

既にServerless Frameworkを使ってAWSにデプロイしているリソースに対してアップデートが必要となり、修正版を本番環境にデプロイする機会が巡ってきました。
検証環境で動作に問題が無いことは確認済ですが、万が一を想定して修正したリソースが正常に動作しなかった時の対処を考えていました。
何かあれば修正前の状態に戻すのが良いと思い調べたところ、Serverless Frameworkにはロールバック用のコマンドが用意されており、これを使って対処できそうだったので使ってみた結果を残しておきたいと思います。

ロールバックの方法

ロールバックに使用するコマンドはsls rollback --timestampというコマンドです。slsserverlessのaliasです。 以下が公式のドキュメントのコマンドの説明になります。

timestampの値はsls deploy listを使うことで今までのデプロイ実行毎の状態とそれに対応するtimestampが表示されるので、その中から戻したいと状態のtimestampを使います。 sls rollbackはロールバックするリソースを個別に指定してロールバックするのではなく、ある時点すべてのリソースをその時の状態に戻すという動きをします。

サンプルで試してみる

サンプルとして用意したリソースとアップデートによる変更内容は以下の通りとなります。

AWSリソース

簡単なREST APIで Lambddaからはステータスコード200とHollo Worldという文字列のレスポンスボディが返るのみです。

  • API Gateway
  • Lambda

アップデートの変更内容

  • Lambdaからレスポンスボディとして返す文字列の内容を変更
    • 'Hello World' --> 'Updated!'
  • Lambdaのランタイムのバージョン
    • Node.js14 --> Node.js18
  • タイムアウト秒数の変更
    • 3 --> 25

現行のLambdaの設定を確認

アップデート前のLambda関数の設定を取得しておきます。 AWS CLIのコマンドで取得します。(長いので折りたたんだ中に入れました。)

今回更新しようとしているRuntimeTimeoutも取得できています。 これらの設定がロールバック後に同様になっているか確認したいと思います。 確認は以下のコマンドでできます。

$ aws lambda get-function --function-name <Lambda関数名>
取得結果
$ aws lambda get-function --function-name serverless-rollback-trial

{
    "Configuration": {
        "FunctionName": "serverless-rollback-trial",
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:serverless-rollback-trial",
        "Runtime": "nodejs14.x", <--ここを変更します
        "Role": "arn:aws:iam::************:role/serverless-rollback-trial-dev-ap-northeast-1-lambdaRole",
        "Handler": "src/handlers/sample/index.handler",
        "CodeSize": 293,
        "Description": "",
        "Timeout": 3, <--ここを変更します
        "MemorySize": 128,
        "LastModified": "2023-04-27T12:25:20.000+0000",
        "CodeSha256": "urACWtzfnxSt894CGNHGeAx9IaPmaegTD5UuNDYUGdY=",
        "Version": "$LATEST",
        "VpcConfig": {
            "SubnetIds": [],
            "SecurityGroupIds": [],
            "VpcId": ""
        },
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "d6c1ef1e-d97b-468e-857e-ada13d5feb0c",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip",
        "Architectures": [
            "x86_64"
        ],
        "EphemeralStorage": {
            "Size": 512
        },
        "SnapStart": {
            "ApplyOn": "None",
            "OptimizationStatus": "Off"
        },
        "RuntimeVersionConfig": {
            "RuntimeVersionArn": "arn:aws:lambda:ap-northeast-1::runtime:cbf1e4541be1b7368dfdccdd8dd4be4f1de07dfdf6da3cab893d919f6b99e9a9"
        }
    },
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/************/serverless-rollback-trial-3272321a-f631-4ff0-8114-1179fe85e064
    },
    "Tags": {
        "aws:cloudformation:stack-name": "serverless-rollback-trial-dev",
        "aws:cloudformation:stack-id": "arn:aws:cloudformation:ap-northeast-1:************:stack/serverless-rollback-trial-dev/4a1a57f0-e4d9-11ed-a25e-0a12404786f5",
        "STAGE": "dev",
        "aws:cloudformation:logical-id": "SampleLambdaFunction"
    }
}

変更後の serverless.yml と lambda関数

serverless.yml

更新後のserverless.ymlは以下のような感じになります。
使用するServerless Frameworkのバージョンはv3となります。
これでAPI Gatewayまで作れてしまうのでなかなかに便利ですね。

serverless.yml

service: serverless-rollback-trial

frameworkVersion: "3"

provider:
  name: aws
  runtime: nodejs18.x  <-- ここを変更しました
  stage: dev
  region: ap-northeast-1

package:
  individually: true
  exclude:
    - "**"

functions:
  sample:
    name: serverless-rollback-trial
    description:
    handler: src/handlers/sample/index.handler
    memorySize: 128
    timeout: 25  <-- ここを変更しました
    package:
      include:
        - src/handlers/sample/*.js
    events:
      - http:
          path: /sample
          method: get
          integration: lambda

変更後のlambda関数

src/handlers/sample/index.js

exports.handler = async () => {
  const response = {
    statusCode: 200,
    body: JSON.stringify('Updated!'), <-- ここを変更しました
  };
  return response;
};

変更したリソースをデプロイ

それでは変更したリソースをデプロイしてみます。

$ sls deploy --verbose

無事にデプロイできましたので設定を確認してみます。 ランタイムとタイムアウト時間が変更されていることが確認できます。

取得結果
$ aws lambda get-function --function-name serverless-rollback-trial

{
  "Configuration": {
      "FunctionName": "serverless-rollback-trial",
      "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:serverless-rollback-trial",
      "Runtime": "nodejs18.x",
      "Role": "arn:aws:iam::************:role/serverless-rollback-trial-dev-ap-northeast-1-lambdaRole",
      "Handler": "src/handlers/sample/index.handler",
      "CodeSize": 292,
      "Description": "",
      "Timeout": 25,
      "MemorySize": 128,
      "LastModified": "2023-04-27T13:25:29.000+0000",
      "CodeSha256": "2uxHvOBNgeuVweVDbG8d5u0Q2+FaTy5O0yCBuISXqcY=",
      "Version": "$LATEST",
      "VpcConfig": {
          "SubnetIds": [],
          "SecurityGroupIds": [],
          "VpcId": ""
      },
      "TracingConfig": {
          "Mode": "PassThrough"
      },
      "RevisionId": "f2fd2513-1c4a-4907-a1fd-a8f9220f214b",
      "State": "Active",
      "LastUpdateStatus": "Successful",
      "PackageType": "Zip",
      "Architectures": [
          "x86_64"
      ],
      "EphemeralStorage": {
          "Size": 512
      },
      "SnapStart": {
          "ApplyOn": "None",
          "OptimizationStatus": "Off"
      },
      "RuntimeVersionConfig": {
          "RuntimeVersionArn": "arn:aws:lambda:ap-northeast-1::runtime:a19cefba18c0aa228bfe71e81d95cc5aab18e4570e899b3cfd9f759f022c7f8c"
      }
  },
  "Code": {
      "RepositoryType": "S3",
      "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/************/serverless-rollback-trial-0746451f-8ea8-495d-af91-b53b3081e3e8
  },
  "Tags": {
      "aws:cloudformation:stack-name": "serverless-rollback-trial-dev",
      "aws:cloudformation:stack-id": "arn:aws:cloudformation:ap-northeast-1:************:stack/serverless-rollback-trial-dev/4a1a57f0-e4d9-11ed-a25e-0a12404786f5",
      "STAGE": "dev",
      "aws:cloudformation:logical-id": "SampleLambdaFunction"
  }
}

API Gatewayのエンドポイントも叩いてみます。

$ curl 'https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/sample'

{"statusCode":200,"body":"\"Updated!\""}

変更したレスポンスボディの内容が返ってきました。

ロールバック

それではロールバックしてみます。 まずはタイムスタンプを確認します。

$ sls deploy list

2023-04-27 12:21:22 UTC
Timestamp: 1682598082581
Files:
  - compiled-cloudformation-template.json
  - sample.zip
  - serverless-state.json

2023-04-27 13:23:52 UTC
Timestamp: 1682601832416
Files:
  - compiled-cloudformation-template.json
  - sample.zip
  - serverless-state.json

Timestampが違う2つのバージョンがあることがわかります。 日付情報が新しいほうが後からデプロイした更新バージョンですので、その上のtimestampが1682598082581のほうが更新前のバージョンとなります。

実際にロールバックしてみます。

$ sls deploy rollback --timestamp 1682598082581

Running "serverless" from node_modules

Rolling back serverless-rollback-trial to timestamp "1682598082581"

✔ Service rolled back to timestamp "1682598082581"

ロールバックが完了しました。

再度、API Gatewayのエンドポイントを叩いてみます。

$ curl 'https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/sample'

{"statusCode":200,"body":"\"Hello World!\""}

変更前のレスポンスに変わっています。

Lambdaの設定を確認してみます。

取得結果
$ aws lambda get-function --function-name serverless-rollback-trial

{
    "Configuration": {
        "FunctionName": "serverless-rollback-trial",
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:serverless-rollback-trial",
        "Runtime": "nodejs14.x", <--変更前に戻ってます
        "Role": "arn:aws:iam::************:role/serverless-rollback-trial-dev-ap-northeast-1-lambdaRole",
        "Handler": "src/handlers/sample/index.handler",
        "CodeSize": 293,
        "Description": "",
        "Timeout": 3, <--変更前に戻ってます
        "MemorySize": 128,
        "LastModified": "2023-04-27T13:48:42.000+0000",
        "CodeSha256": "urACWtzfnxSt894CGNHGeAx9IaPmaegTD5UuNDYUGdY=",
        "Version": "$LATEST",
        "VpcConfig": {
            "SubnetIds": [],
            "SecurityGroupIds": [],
            "VpcId": ""
        },
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "fb659749-989f-4125-8031-0b551bf7351a",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip",
        "Architectures": [
            "x86_64"
        ],
        "EphemeralStorage": {
            "Size": 512
        },
        "SnapStart": {
            "ApplyOn": "None",
            "OptimizationStatus": "Off"
        },
        "RuntimeVersionConfig": {
            "RuntimeVersionArn": "arn:aws:lambda:ap-northeast-1::runtime:cbf1e4541be1b7368dfdccdd8dd4be4f1de07dfdf6da3cab893d919f6b99e9a9"
        }
    },
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/************/serverless-rollback-trial-b0fd73e6-bce0-41e5-85aa-083496e4b2f7
    },
    "Tags": {
        "aws:cloudformation:stack-name": "serverless-rollback-trial-dev",
        "aws:cloudformation:stack-id": "arn:aws:cloudformation:ap-northeast-1:************:stack/serverless-rollback-trial-dev/4a1a57f0-e4d9-11ed-a25e-0a12404786f5",
        "STAGE": "dev",
        "aws:cloudformation:logical-id": "SampleLambdaFunction"
    }
}

"Runtime": "nodejs14.x","Timeout": 3,が変更前の設定の戻っていますので、問題なく元の状態にロールバックされてます。 ということで、もともとの目的には使用できそうです。
使用するような事にはならないようにアップデートできるのが一番ですが、万が一の時の手段ができて良かったです。

以上。