タケユー・ウェブ日報

Ruby on Rails や Flutter といったWeb・モバイルアプリ技術を武器にお客様のビジネス立ち上げを支援する、タケユー・ウェブ株式会社の技術ブログです。

EC2 ImageBuilder コンポーネントのデバッグ

まとめ

  • Terminate instance on failurefalse にしてもテスト用のインスタンスは殺される
  • S3へのログ保存は絶対に有効にしろ
  • ステップはなるべく小分けに

事の起こり

  • EC2 ImageBuilder でゴールデンAMIを作っている
  • ある日昨日まで成功してたテストをパスしなくなった
  • エラー内容を見ても failed to run commands: exit status 1 のようなのでよくわからない

f:id:uzuki05:20200329090414p:plain
EC2 ImageBuilder で謎のエラー

デバッグ

AWS Systems Manager オートメーション

EC2 ImageBuilder のタスクは、AWS Systems Manager オートメーションを利用して動いています。 [AWS Systems Manager] => [アクションと変更] => [自動化] から自動化の実行の一覧を確認します。

  • ImageBuilderBuildImageDocument
  • ImageBuilderTestImageDocument

がそれぞれ、ビルド、テストに使ったドキュメントです。

ですが、ここだけ見てもエラーの詳細はさっぱりわからないので後に書く S3 へのログのアップロードを有効化しておく必要があります。

f:id:uzuki05:20200329092454p:plain
エラー画面

Image Pipeline の Troubleshooting settings

Image Pipeline を作るとき、変更するときに指定できるデバッグ用のオプションが用意されています。 デフォルトでは無効になっているので注意しましょう。

Terminate instance on failure

デフォルトで true チェックを外すとビルド失敗時にビルドに使ったEC2インスタンスを起動したままにしておいてくれるので、実際に環境にログインして調査することができます。

ただ、テストフェーズで失敗した場合は機能しないようです。

なお、EC2インスタンスはプライベートサブネットに作成されるため、SSHには踏み台が必要です。

Key pair

EC2インスタンスに設定するSSHのキーペア。

Logs

設定すると、自動化処理中の以下のようなログを指定したS3バケットにアップロードしてくれます。 Image pipelineの実行IAM roleに指定したS3バケットへのPut権限をつける必要があると思います。 S3バケット中のフォルダ名は、AWS Systems Manager オートメーションの自動化の実行IDを元にした名前になるので、まず実行IDを特定して、S3を探すことになります。

実行IDは EC2 Image Builder images の実行の詳細画面 Reason for failure に書いてあります。 または、AWS Systems Manager オートメーションの自動化の一覧から探してもよいでしょう。

f:id:uzuki05:20200329095417p:plain

f:id:uzuki05:20200329093017p:plain

ログフォルダの中身は以下のような内容になっています。

detailedoutput.json

どのステップで失敗したのか?どんなコマンドを実行したのか?がわかります。

{
    "executionId": "02bb195c-7143-11ea-a1f0-0603d891b362",
    "status": "failed",
    "startTime": "2020-03-29T07:25:22+09:00",
    "endTime": "2020-03-29T07:27:32+09:00",
    "failureMessage": "Document TOE_2020-03-28_22-25-21_UTC-0_02bb195c-7143-11ea-a1f0-0603d891b362/0__myapp-install__0.0.7_1.yml failed!",
    "documents": [
        {
            "name": "MyApp Install",
            "filePath": "TOE_2020-03-28_22-25-21_UTC-0_02bb195c-7143-11ea-a1f0-0603d891b362/0__myapp-install__0.0.7_1.yml",
            "status": "failed",
            "description": "This is MyApp Install testing document.",
            "startTime": "2020-03-29T07:25:22+09:00",
            "endTime": "2020-03-29T07:27:32+09:00",
            "failureMessage": "Phase test failed!",
            "phases": [
                {
                    "name": "test",
                    "status": "failed",
                    "startTime": "2020-03-29T07:25:22+09:00",
                    "endTime": "2020-03-29T07:27:32+09:00",
                    "failureMessage": "Step CheckResponse failed!",
                    "steps": [
                        {
                            "name": "HelloWorldStep",
                            "status": "success",
                            "failureMessage": "",
                            "timeoutSeconds": 7200,
                            "onFailure": "Abort",
                            "maxAttempts": 1,
                            "action": "ExecuteBash",
                            "startTime": "2020-03-29T07:25:22+09:00",
                            "endTime": "2020-03-29T07:25:22+09:00",
                            "inputs": "[{\"commands\":[\"echo \\\"Hello World! Test.\\\"\"]}]",
                            "outputs": "[{\"stdout\":\"Hello World! Test.\"}]"
                        }
                        {
                            "name": "CheckResponse",
                            "status": "failed",
                            "failureMessage": "exit status 244",
                            "timeoutSeconds": 7200,
                            "onFailure": "Abort",
                            "maxAttempts": 3,
                            "action": "ExecuteBash",
                            "startTime": "2020-03-29T07:27:26+09:00",
                            "endTime": "2020-03-29T07:27:32+09:00",
                            "inputs": "[{\"commands\":[\"echo \\\"curl -LI  http://localhost/\\\"\\nSTATUS_CODE=$(curl -LI  http://localhost/ -o /dev/null -w '%{http_code}\\\\n' -s)\\nif [[ $STATUS_CODE == \\\"200\\\" ]]; then\\n    echo \\\"MyApp was successfully invoked.\\\"\\nelse\\n    echo \\\"MyApp was not successfully invoked. Failing.\\\"\\n    exit $STATUS_CODE\\nfi\\n\"]}]",
                            "outputs": "null"
                        },
console.log

どのステップで失敗したのか?どんなコマンドを実行したのか?がわかります。

2020-03-29 07:25:22 Info Document TOE_2020-03-28_22-25-21_UTC-0_02bb195c-7143-11ea-a1f0-0603d891b362/0__myapp-install__0.0.7_1.yml
2020-03-29 07:25:22 Info Phase test
2020-03-29 07:25:22 Info Step HelloWorldStep
2020-03-29 07:25:22 Info Command execution completed successfully
2020-03-29 07:25:22 Info Stdout: Hello World! Test.
2020-03-29 07:25:22 Info Stderr: 
2020-03-29 07:25:22 Info ExitCode 0
2020-03-29 07:27:26 Info Step CheckMonitorStatus
2020-03-29 07:27:27 Info Command execution resulted in an error
2020-03-29 07:27:27 Info Stdout: curl -LI  http://localhost/
MyApp was not successfully invoked. Failing.
2020-03-29 07:27:27 Info Stderr: 
2020-03-29 07:27:27 Info ExitCode 244
2020-03-29 07:27:29 Info Command execution resulted in an error
2020-03-29 07:27:29 Info Stdout: curl -LI  http://localhost/
MyApp was not successfully invoked. Failing.
2020-03-29 07:27:29 Info Stderr: 
2020-03-29 07:27:29 Info ExitCode 244
2020-03-29 07:27:31 Info Command execution resulted in an error
2020-03-29 07:27:31 Info Stdout: curl -LI  http://localhost/
MyApp was not successfully invoked. Failing.
2020-03-29 07:27:31 Info Stderr: 
2020-03-29 07:27:31 Info ExitCode 244

なお、今回の原因

ログを確認したところ、テストフェーズでdockerのインストールなどを行ったこともあり、disk full になったのが原因でした。 ImageBuilder で使うEC2インスタンスのストレージサイズを変更したレシピを作成したところ成功しました。

AMI(EBSスナップショット)の使用量と料金を確認する

まとめ

  • ボリュームサイズとスナップショットの使用量は別
  • スナップショットの使用量を知るにはコストエクスプローラーを使う
  • EBS:SnapshotUsage (GB-Month) 使用タイプとコスト配分タグを組み合わせる

事の起こり

現在、EC2 ImageBuilder で作成したGolden AMIを使った Immutable Infrastructure を構成し、運用しています。 この運用だと、アプリケーションコードの更新やセキュリティアップデートなどで頻繁にAMIを作成することになります。

AMIはEBSスナップショットと組なので、AMIをたくさん作るということは、EBSスナップショットをたくさん作るということになります。

Amazon マシンイメージ (AMI)

個々のスナップショットの容量が数GB、それがたくさんともなれば心配になるのは費用です。

f:id:uzuki05:20200327180719p:plain

「EBSスナップショットの課金容量=ボリュームサイズ」ではない

ドキュメントによれば、スナップショットの課金額は、

  • ストレージの実際に使っているブロックを
  • 圧縮して
  • 作成元のEBSスナップショットからの差分

になるようです。

では、実際に請求対象となる「データを Amazon S3 で保存するのに使用された領域の大きさ」はどこで見られるのでしょう?

参考

スナップショットストレージの基盤になるのは、データを Amazon S3 で保存するのに使用された領域の大きさです。Amazon EBS では空のブロックが保存されないため、スナップショットのサイズはボリュームサイズよりもかなり少なくなるはずです。ボリュームの最初のスナップショットについては、データ全体のコピーが Amazon S3 に保存されます。増分スナップショットごとに、Amazon EBS ボリュームの変更部分のみが保存されます。

Amazon EBS の価格

Snapshotに関しては、EBSボリュームの実サイズがそのまま計算されるのではなく、 圧縮してコピーされ、増分については変更部分のみ保存されます。 (費用をできる限り軽減するようにさせて頂いております。) 

AWS Developer Forums: EBSスナップショットについて ...

Amazon EBS-Backed の AMI の場合、AMI をカスタマイズしたり、新しい AMI を作成したりするたびに、変更のみが保存されます。そのため、最初の AMI の後にカスタマイズする後続の AMI のストレージフットプリントははるかに小さくなり、AMI ストレージ料金が少なくなります。

AMI タイプ

コストエクスプローラーを使う

コストエクスプローラー「使用タイプ」で「EBS:SnapshotUsage (GB-Month)」を選ぶと、スナップショットの「Total usage (GB-Month)」と「Total cost ($)」を確認できます。

f:id:uzuki05:20200327182440p:plain

これだけだと、すべてのスナップショットの合計になるので、特定のスナップショットの使用量を知るには、コスト配分タグを使います。

  1. EBSスナップショット(AMIではない)にタグを付ける(例 RailsEnv=production Service=app CommitID=0123456789abcdef など)
  2. 作成したタグを「ユーザー定義のコスト配分タグ」としてアクティブ化
  3. コストエクスプローラーの「タグ」で選択

このようにEBS:SnapshotUsage (GB-Month) 使用タイプコスト配分タグを組み合わせることで、AMI(EBSスナップショット)の使用量と料金を確認することができます。

コスト配分タグ

コスト配分タグは、AWSの各リソースに設定した「タグ」をもとに、タグごとの利用状況・コストを分析できる機能です。

ただし、リソースに設定したタグはそれだけではコスト配分タグとしては利用できず、別途、「ユーザー定義のコスト配分タグのアクティブ化」の操作が必要です。

ユーザー定義のコスト配分タグのアクティブ化

aws.amazon.com

AmazonLinux2にgcc6をインストール(標準のgcc7と共存させる)

まとめ

  • AmazonLinux2 標準の gcc は 7.x
  • gcc 6.x を使いたいが汚したくない
  • Environment modules で使いたいものだけ使う

事の起こり

事情によりHyper Estraierをインストールする必要があったのですが、通常通りに make すると QDBM が Segmentation fault して動きませんでした。

[root@ip-10-0-1-37 hyperestraier-1.4.13]# make check rm -rf casket casket- LD_LIBRARY_PATH=.:/lib:/usr/lib:/usr/local/lib:/root/lib:/usr/local/lib::/usr/local/lib ./estcmd create -tr -xl -attr '@uri' seq -attr '@title' str \ -attr '@author' str -attr '@mdate' num -attr '@size' num casket ./estcmd: INFO: status: name=casket dnum=0 wnum=0 fsiz=19924864 crnum=0 csiz=0 dknum=0 make: ** [check] Segmentation fault

[root@ip-10-0-1-37 qdbm-1.8.78]# make check (snip) rm -rf casket* LD_LIBRARY_PATH=.:/lib:/usr/lib:/usr/local/lib:/root/lib:/usr/local/lib ./odtest write casket 500 50 5000 name=casket dnum=500 wnum=50 pnum=5000 ibnum=-1 idnum=-1 cbnum=-1 csiz=-1

.make: *** [check] Segmentation fault

いろいろ情報をあさっていると、似た報告がありました。どうやら gcc 7 だと発生し、 gcc 6 なら発生しないようです。

estcmd built with gcc-7.2.0 caused segfault. I sent mails to the author, but I couldn't get a reply.

http://linuxplayers.g1.xrea.com/mozc-ut2.html

AmazonLInux2 の gcc

gcc version 7.3.1 20180712 (Red Hat 7.3.1-6) (GCC)

このgccは維持しながら、必要なときに gcc 6 を使えるようにしたい。

Environment modules

Environment modules が使えそうです。

参考 Environment modules事始め

Environment modules のインストール

$ sudo yum install environment-modules

gcc6 のインストール

yum install autogen

wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-6.5.0/gcc-6.5.0.tar.gz -O /tmp/gcc-6.5.0.tar.gz tar xvf /tmp/gcc-6.5.0.tar.gz -C /usr/local/src cd /usr/local/src/gcc-6.5.0 ./contrib/download_prerequisites mkdir build && cd build ../configure --enable-languages=c,c++ --prefix=/usr/local/gcc-6.5.0 --disable-bootstrap --disable-multilib make && make check && make install

ソースディレクトリ内で configure しない理由 https://gcc.gnu.org/wiki/FAQ#configure

gcc6 を Environment modules で使うための設定

$ vi /etc/modulefiles/gcc-6.5.0
#%Module 1.0
#
#  gcc-6.5.0 module for use with 'environment-modules' package:
#

set GCChome /usr/local/gcc-6.5.0

prepend-path    PATH             $GCChome/bin
prepend-path    INCLUDE        $GCChome/include
prepend-path    LD_LIBRARY_PATH $GCChome/lib
prepend-path    MANPATH        $GCChome/man

使い方

モジュール一覧

$ module avail

----------------------- /usr/share/Modules/modulefiles ------------------------
dot         module-git  module-info modules     null        use.own

------------------------------ /etc/modulefiles -------------------------------
gcc-6.5.0

モジュールを使う

$ module add gcc-6.5.0
$ gcc --version
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/gcc-6.5.0/libexec/gcc/x86_64-pc-linux-gnu/6.5.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../configure --enable-languages=c,c++ --prefix=/usr/local/gcc-6.5.0 --disable-bootstrap --disable-multilib
Thread model: posix
gcc version 6.5.0 (GCC)

使い終わったらアンロード

$ module unload gcc-6.5.0
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/7/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --enable-libmpx --enable-libsanitizer --enable-gnu-indirect-function --enable-libcilkrts --enable-libatomic --enable-libquadmath --enable-libitm --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 7.3.1 20180712 (Red Hat 7.3.1-6) (GCC)

Rais.env.development? みたいな文字列がその値か?真偽値をとれるやつを使いたい

こういうのです。

Rails.env.production? # Rails.env == "production" と同じ

ActiveSupport::StringInquirer クラスを使います。

role = ActiveSupport::StringInquirer.new("admin")
role.admin? #=> true

ActiveSupport::StringInquirer

activesupport/lib/active_support/string_inquirer.rb

String のサブクラスで、respond_to_missing?method_missing を使っていますね。

メソッド名が ? で終わっていたら method_missing が呼ばれ、メソッド名の ? より前の部分文字列が自身と同じなら真を返すわけですね。

Active::Recordのトランザクション復習

user = User.find(2)
user.name = 'takeyuweb'
user.save!

User.transaction do         # A
  user.name = 'takeyuweb(trA)'
  user.save!
  
  User.transaction do       # B
    user.name = 'takeyuweb(trB)'
    user.save!
  end
  User.transaction do   # C
    user.name = 'takeyuweb(trC)'
    user.save!
    raise
  end
end

pp user.reload.name # => "takeyuweb"

user = User.find(2)
user.name = 'takeyuweb'
user.save!

User.transaction do         # A
  user.name = 'takeyuweb(trA)'
  user.save!
  
  User.transaction do       # B
    user.name = 'takeyuweb(trB)'
    user.save!
  end
  
  User.transaction(requires_new: true) do   # C
    user.name = 'takeyuweb(trC)'
    user.save!
    raise("突き抜けろ!")
  end
end
# RuntimeError: 突き抜けろ!

pp user.reload.name # => "takeyuweb"

--

user = User.find(2)
user.name = 'takeyuweb'
user.save!

User.transaction do         # A
  user.name = 'takeyuweb(trA)'
  user.save!
  
  User.transaction do       # B
    user.name = 'takeyuweb(trB)'
    user.save!
  end
  
  User.transaction(requires_new: true) do   # C
    user.name = 'takeyuweb(trC)'
    user.save!
    raise(ActiveRecord::Rollback)
  end
end
# CのActiveRecord::Rollbackは握りつぶされてそのまま処理を継続するので

pp user.reload.name # => "takeyuweb(trB)"

user = User.find(2)
user.name = 'takeyuweb'
user.save!

User.transaction do         # A
  User.transaction do       # B
    user.name = 'takeyuweb(trB)'
    user.save!
  end
  
  User.transaction(requires_new: true) do   # C
    user.name = 'takeyuweb(trC)'
    user.save!
    raise(ActiveRecord::Rollback)
  end
  
  user.name = 'takeyuweb(trA)'
  user.save!
end

pp user.reload.name # => "takeyuweb(trA)"

こうなる。

そのまま処理を継続してしまうとよくわかって使うこと

ruby aws sdk v2 SimpleWorkFlow(Aws::SWF::Client)メモ

swf = Aws::SWF::Client.new

失敗したExecutionを取り出す

WORKFLOW_DOMAIN = 'myworkflowdomain'

resp = swf.list_closed_workflow_executions(
         domain: WORKFLOW_DOMAIN,
         start_time_filter: {oldest_date: 24.hours.ago}, 
         close_status_filter: {status: 'TIMED_OUT'})
resp.execution_infos.each do |execution_info|
  pp execution_info
  # #<struct Aws::SWF::Types::WorkflowExecutionInfo
  #   execution=
  #    #<struct Aws::SWF::Types::WorkflowExecution
  #     workflow_id="fcac73cf-51c2-43f7-95cd-387a0bb4338b",
  #     run_id="22I3gtPVgL2GuGcBd9q1YvqH0D4aY66UbeQrJh+h7ExI8=">,
  #   workflow_type=
  #    #<struct Aws::SWF::Types::WorkflowType
  #     name="EncodeWorkflow.encode",
  #     version="1.20">,
  #   start_timestamp=2016-05-19 18:21:53 +0000,
  #   close_timestamp=2016-05-19 19:21:53 +0000,
  #   execution_status="CLOSED",
  #   close_status="TIMED_OUT",
  #   parent=nil,
  #   tag_list=[],
  #   cancel_requested=false>

  execution_event = swf.get_workflow_execution_history(
           domain: WORKFLOW_DOMAIN, 
           execution: execution_info.execution.to_h, 
           maximum_page_size: 1, 
           reverse_order: true).events.first
  pp execution_event
  # #<struct Aws::SWF::Types::HistoryEvent
  #  event_timestamp=2016-05-19 19:21:53 +0000,
  #  event_type="WorkflowExecutionTimedOut",
  #  event_id=183,
  #  workflow_execution_started_event_attributes=nil,
  #  workflow_execution_completed_event_attributes=nil,
  #  complete_workflow_execution_failed_event_attributes=nil,
  #  workflow_execution_failed_event_attributes=nil,
  #  fail_workflow_execution_failed_event_attributes=nil,
  #  workflow_execution_timed_out_event_attributes=
  #   #<struct Aws::SWF::Types::WorkflowExecutionTimedOutEventAttributes
  #    timeout_type="START_TO_CLOSE",
  #    child_policy="TERMINATE">,
  #  workflow_execution_canceled_event_attributes=nil,
  #  cancel_workflow_execution_failed_event_attributes=nil,
  #  workflow_execution_continued_as_new_event_attributes=nil,
  #  continue_as_new_workflow_execution_failed_event_attributes=nil,
  #  workflow_execution_terminated_event_attributes=nil,
  #  workflow_execution_cancel_requested_event_attributes=nil,
  #  decision_task_scheduled_event_attributes=nil,
  #  decision_task_started_event_attributes=nil,
  #  decision_task_completed_event_attributes=nil,
  #  decision_task_timed_out_event_attributes=nil,
  #  activity_task_scheduled_event_attributes=nil,
  #  activity_task_started_event_attributes=nil,
  #  activity_task_completed_event_attributes=nil,
  #  activity_task_failed_event_attributes=nil,
  #  activity_task_timed_out_event_attributes=nil,
  #  activity_task_canceled_event_attributes=nil,
  #  activity_task_cancel_requested_event_attributes=nil,
  #  workflow_execution_signaled_event_attributes=nil,
  #  marker_recorded_event_attributes=nil,
  #  record_marker_failed_event_attributes=nil,
  #  timer_started_event_attributes=nil,
  #  timer_fired_event_attributes=nil,
  #  timer_canceled_event_attributes=nil,
  #  start_child_workflow_execution_initiated_event_attributes=nil,
  #  child_workflow_execution_started_event_attributes=nil,
  #  child_workflow_execution_completed_event_attributes=nil,
  #  child_workflow_execution_failed_event_attributes=nil,
  #  child_workflow_execution_timed_out_event_attributes=nil,
  #  child_workflow_execution_canceled_event_attributes=nil,
  #  child_workflow_execution_terminated_event_attributes=nil,
  #  signal_external_workflow_execution_initiated_event_attributes=nil,
  #  external_workflow_execution_signaled_event_attributes=nil,
  #  signal_external_workflow_execution_failed_event_attributes=nil,
  #  external_workflow_execution_cancel_requested_event_attributes=nil,
  #  request_cancel_external_workflow_execution_initiated_event_attributes=nil,
  #  request_cancel_external_workflow_execution_failed_event_attributes=nil,
  #  schedule_activity_task_failed_event_attributes=nil,
  #  request_cancel_activity_task_failed_event_attributes=nil,
  #  start_timer_failed_event_attributes=nil,
  #  cancel_timer_failed_event_attributes=nil,
  #  start_child_workflow_execution_failed_event_attributes=nil,
  #  lambda_function_scheduled_event_attributes=nil,
  #  lambda_function_started_event_attributes=nil,
  #  lambda_function_completed_event_attributes=nil,
  #  lambda_function_failed_event_attributes=nil,
  #  lambda_function_timed_out_event_attributes=nil,
  #  schedule_lambda_function_failed_event_attributes=nil,
  #  start_lambda_function_failed_event_attributes=nil>
end

再実行

# 最初のイベントにワークフロー開始時の属性が入っているのでそれを使うことにした
execution_started_event_attributes = swf.get_workflow_execution_history(
           domain: WORKFLOW_DOMAIN, 
           execution: execution_info.execution.to_h, 
           maximum_page_size: 1, 
           reverse_order: false).events.first.workflow_execution_started_event_attributes

re_run_id = swf.start_workflow_execution({
  domain: WORKFLOW_DOMAIN,
  workflow_id: execution_info.execution.workflow_id,
  workflow_type: execution_info.workflow_type.to_h,
  task_list: execution_started_event_attributes.task_list,
  task_priority: execution_started_event_attributes.task_priority,
  input: execution_started_event_attributes.input,
  execution_start_to_close_timeout: execution_started_event_attributes.execution_start_to_close_timeout,
  tag_list: execution_started_event_attributes.tag_list,
  task_start_to_close_timeout: execution_started_event_attributes.task_start_to_close_timeout,
  child_policy: execution_started_event_attributes.child_policy,
  lambda_role: execution_started_event_attributes.lambda_role,
}).run_id

pp re_run_id # => "22K7jh/0kr9D4MGohM6aAESvZXkwuJg8WN3dN28silj1U="