📖

re:Invent 2023: AWSチームがECRの内部動作と最新機能を深掘り解説

2023/11/28に公開

はじめに

海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!

📖 AWS re:Invent 2023 - Dive deep into Amazon ECR (CON405)

この動画では、Amazon ECRの内部動作と最新機能について詳しく解説しています。AWSのPrincipal EngineerであるMike AlfreeとAmazon ECRのPMであるRafa Alvarezが、ECRの裏側で行われる複雑なプロセスを明かします。毎月数十億回のpullを処理し、3エクサバイト以上のデータを提供するECRの驚異的なスケールや、レプリケーション、スキャン、Lifecycle Policiesなどの機能の仕組みを学べます。さらに、今後のロードマップについても触れており、ECRの進化を垣間見ることができます。
https://www.youtube.com/watch?v=PHuKrcsAaDw
※ 動画から自動生成した記事になります。誤字脱字や誤った内容が記載される可能性がありますので、正確な情報は動画本編をご覧ください。

本編

Amazon ECRサービス概要:Rafa AlvarezとMike Alfreeによる紹介

Thumbnail 0

みなさん、ようこそ。お昼休みの時間にお集まりいただき、ありがとうございます。RafaとI私は、皆さんに私たちのサービスについてお話しできることをとても楽しみにしています。私はMike Alfreeと申します。AWSのPrincipal Engineerです。では、Rafaに引き継ぎます。彼から始めてもらいましょう。

みなさん、こんにちは。私もここにいられて嬉しいです。私はRafa Alvarezと申します。Amazon ECRのPMの一人です。Mikeが言ったように、このサービスの概要や仕組み、そして内部の動作について少しお話しします。今日は、コンテナ化されたフィクションのアプリケーションを例に考えてみましょう。私のラップトップからECRへ、そして様々なサービスへと至る過程で、ECRがどのように関わっているかを見ていきます。

コンテナ化されたCard Whisperer botアプリケーションの構築とAmazon ECRの役割

Thumbnail 50

Thumbnail 70

では、この会話のために、私たちが作成したアプリケーションについて考えてみましょう。繰り返しますが、これはフィクションですが、ラスベガスにいるので、ポーカーをプレイしたいと思いました。でも、私たちはあまり上手ではないので、Generative AIを使って素敵なCard Whisperer botを作って助けてもらうことにしました。 現代的なアプリケーション開発者として、これをコンテナ化された方法で構築することにしました。

画面に表示されているのは、おそらく皆さんにとってとてもなじみ深いものでしょう。私のコードがあります。これは一見シンプルに見えます。そして、アプリケーションの構築方法やコンテナイメージの作成方法の詳細が記載されたDockerfileがあります。Dockerのような広く知られたツールを使用して、コンテナイメージをビルドできます。ご存じの通り、コンテナイメージには実行に必要なものがすべて含まれています。今、私のラップトップ上にあるのは基本的にコンテナイメージで、これを使ってテストや実行などができます。

Thumbnail 110

Thumbnail 120

次のステップは何でしょうか?もちろん、Amazon EKSやAmazon ECSのワークロードに組み込む必要があります。そのためには、レジストリが必要です。 これらのサービスが必要なときにスケールしてコンテナイメージを取得できるようにするツールが必要です。それがAmazon ECR(Elastic Container Registry)の役割です。 基本的に、Amazon EKS、Amazon ECS、その他のコンテナサービスが、AWSのどの場所でも迅速かつスケーラブルにイメージをプルできるようサポートしています。

Amazon ECRへのイメージプッシュプロセスとアーキテクチャ

Thumbnail 140

では、そうしましょう。私のラップトップに画像があります。これをAmazon ECRにプッシュします。コンテナイメージについて興味深いのは、それらを管理する方法について、業界で広く知られた標準があることです。それはOCIです。今、Amazon ECRに新しいリポジトリを作成すると、上部のバナーをクリックすることで、イメージをプッシュするために必要なOCIコマンドが表示されます。イメージにタグを付け、プッシュすると、それがレジストリに保存されます。では、実際にやってみましょう。

Thumbnail 160

左上に私のターミナルが見えます。イメージにタグを付けてからプッシュしました。そして、新しいバージョンをビルドしていくたびに、新しいイメージをAmazon ECRにプッシュし続けます。この講演のために、セマンティックバージョニングを使用して多くのバージョンを作成しました。これにより、メジャーな変更やマイナーな変更を明確に把握できます。この時点で私が持っているのは、一連のイメージで、それらはすべてAmazon ECRの私のリポジトリに保存されており、私のワークロードを実行するために利用可能です。

Thumbnail 190

では、これはどのように機能したのでしょうか?他のAWSサービスとは異なり、Amazon ECRの興味深い点は、この場合、主に(ただし完全にではありませんが)フロントエンドと直接やり取りするのではなく、OCIコマンドを解釈するプロキシサービスと通信したことです。Docker pushを実行すると、プロキシサービスがコマンドを受け取り、AWSサービスのフロントエンドサービスが理解できるAPIコールに変換し、その後、バックエンドの残りの部分と連携します。ストレージにはAmazon S3、メタデータにはAmazon DynamoDBを使用していますが、これらは皆さんにとって目新しいものではないでしょう。しかし、これはAmazon ECRを少し異なるものにする詳細であり、顧客にとって機能する方法でその通信を管理する必要があります。

コンテナイメージの構造とAmazon ECRの内部動作

では、Mikeがこの先について少し詳しく説明してくれるでしょう。

Thumbnail 250

ありがとう、Rafa。前のスライドでは、簡略化されたアーキテクチャが見えましたが、これからより深く掘り下げていく際に、そのモデルを使用します。ここでは一歩下がって、コンテナイメージとは何かについて話したいと思います。それはとてもシンプルです。マニフェストがあり、これは通常、中身を説明するJSONオブジェクトです。レイヤー、レイヤーの数、レイヤーのサイズなどが含まれ、そしてレイヤー自体がコンテナイメージを構成します。レイヤーは、ファイルシステムのようなものと考えることができ、レイヤーがマージされてから、そこからコンテナを実行します。これが、コンテナイメージの概要であり、Amazon ECRが保存するものです。

Thumbnail 290

それでは、前のスライドで見たサービスが、Rafaのラップトップからこのデータを取得し、AWSに保存する方法について説明していきます。ここでマニフェストの中身を見ることができます。これはマニフェストの例で、Rafaのマニフェストです。ご覧のように、スキーマバージョンがあり、JSONであることを示しています。ダイジェストもありますが、これについては後ほど詳しく説明します。そして、レイヤー自体とそのダイジェストがあります。

Thumbnail 330

レイヤー自体にはダイジェストがあり、さらにレイヤーのサイズも記載されています。コンテナイメージは、数十メガバイトから数ギガバイト、あるいは数十ギガバイトまで、大小さまざまです。Amazon ECRはこれらを可能な限り効率的に保存します。コンテナイメージのもう一つの概念がタグです。Rafaが言及したように、彼はセマンティックバージョニングを使用していました。タグは多対一の関係にもなり得ます。ここにV3タグの例があります。最新のマイナーバージョン、最新のパッチバージョン、または最新バージョンを受け入れるかどうかによって、異なるフォーマットがあります。これらのタグは好きなだけ使用できます。「latest」がデフォルトですが、自分で好きなタグを定義することもできます。ECRはこれらの情報をすべて保存してインデックス化する必要があります。

Thumbnail 360

アーキテクチャ図に戻りましょう。Rafaが言及したように、彼のラップトップからDockerを使用していますが、これは私たちのプロキシサービスと通信します。プロキシサービスは、OCIプロトコルを理解するHTTPサーバーのようなものです。一方で、フロントエンドサービスがあります。これはAWSでよく見られるパターンです。CLIやAPIを使用して操作するのがこのサービスです。DockerからのリクエストをHTTP経由でプロキシサービスが受け取り、それをフロントエンドサービスに転送します。ここでは、CLIやSDKから直接呼び出すことができるのと同じAPIを使用します。そこから、メタデータサービスと呼ばれるコンポーネントがあります。これがすべてのインデックス作成、タグやメタデータの管理を行います。この情報はAmazon DynamoDBに保存します。マニフェスト(これもblobとみなされ、JSONオブジェクトです)を含むblobオブジェクト自体は、大きなレイヤーと共にAmazon S3に保存されます。

Amazon ECRのプッシュ操作の詳細と規模

Thumbnail 420

Thumbnail 450

ここで図を少し変えて、シーケンス図に切り替えます。これにより、Rafaのラップトップからの呼び出しがECRまでどのように流れるかを説明できます。先ほど述べたように、これはすべての呼び出しがどのように機能するかを示す一般的なスライドです。プロキシサービスとフロントエンドを経由し、Docker、Helm、またはOCI準拠の任意のクライアントが私たちのプロキシサービスと通信できるようになっています。プロキシサービスはそれをフロントエンドに変換します。フロントエンドサービスの役割は主に3つあります:認証、認可、スロットリングです。IAMポリシーをチェックし、トークンが有効であることを確認し、レジストリとリポジトリに設定されたアクションを実行できることを確認します。最終的に、フロントエンドの大きな役割としてリクエストのスロットリングがあります。

Thumbnail 480

ここで具体的な数字を見てみましょう。これはプッシュ側の数字です。ECRは月に非常に多くのデータを処理します。月に約2億回のプッシュを処理しており、この1時間の講演中だけでも25万回に相当します。数百ペタバイトのデータ量です。先ほど述べたように、スロットリングは重要です。すべてのAWSサービスがこれを行っています。サービス制限を設定することで、私たちと顧客が互いに干渉しないよう保護し、公平な計算リソースの共有を確保しています。これらは、実質的にプッシュAPIを支えるサービス制限の例です。

Thumbnail 530

Rafaがラップトップで行ったプッシュは一つのステップのように見えましたが、実際にはDockerクライアントからECRへの複数の呼び出しが裏で行われています。これらは私たちのAPIの名前です。次のスライドでOCI仕様における詳細を説明しますが、ここにリストがあります。興味深いのは、実際にはレイヤーから始まり、マニフェストで終わることです。プッシュは一種の進行操作と考えることができます。これにより、この場合Dockerであるクライアントは、中断された場合に再開できるような方法でデータを私たちにプッシュできます。まずすべてのレイヤーを送信し、最後にマニフェストで完了させ、イメージが完成したことを知らせます。

Amazon ECRのレイヤーアップロードプロセスとセキュリティ対策

Thumbnail 570

Thumbnail 590

最初のステップから始めましょう。私たちの側ではこれをCheck Layer Availabilityと呼んでいます。Dockerクライアントは、私たちがそのレイヤーを持っているかどうかを知りたいのです。もし持っていれば、帯域幅の無駄や余分な処理を避けるために、再度送信したくありません。そこで、私たちに問い合わせるためにこの呼び出しを行います。その方法は、Docker内部から私たちのプロキシサービスへのHTTP HEADリクエストです。プロキシサービスは、BatchCheckLayerAvailability APIを呼び出します。「バッチ」と呼ばれていますが、プロキシサービスからの場合、項目は1つだけです。お客様は実際に複数の項目を入れて直接呼び出すこともできますが、Dockerがプロキシと通信する場合は1項目のみです。

Thumbnail 610

Thumbnail 630

フロントエンドサービスは、その呼び出しをメタデータサービスに転送し、メタデータサービスはDynamoDBをチェックして、そのダイジェストを持っているかどうかを確認します。ダイジェストは一意で不変なので、変更できません。データベースにあれば、クライアントに持っているかどうかを伝えることができます。200レスポンスは見つかった、持っているという意味で、404は見つからない、持っていないという意味です。これは典型的なHTTPプロトコルの仕組みです。HEADリクエストとレスポンスで見えるレイヤーはOCI仕様です。それ以外はAmazon ECRの内部の仕組みです。

Thumbnail 650

Thumbnail 680

Thumbnail 690

Thumbnail 710

次のステップに進みましょう。Dockerにレイヤーがないと伝えられた場合、つまり404レスポンスの場合、最初のレイヤーアップロードを開始します。興味深いのは、ほとんどのクライアントがこれらを並行して行うことです。20のレイヤーがある場合、一度に最大5つを並行して行います。ここでは1つが起こっているように見ていきますが、これが複数回並行して起こる可能性があることを覚えておいてください。この場合、DockerはPOSTリクエストを送信して、ECR内にblobを作成したいことを伝えます。私たちはそれをInitiateLayerUpload API呼び出しに変換し、メタデータサービスに送信します。今度はDynamoDBではなく、Amazon S3をblobデータ用に準備します。S3に対して、blobをアップロードする準備をするよう伝えています。この時点ではリクエストにバイトはありません。これは単にバイトを送信しようとしていることを伝えているだけです。これは一種の進行状況追跡メカニズムです。クライアントにアップロードIDを伝え、クライアントはそのアップロードIDを使用して私たちにバイトを送信します。

Thumbnail 720

次のステップはバイトを送信することで、私たちはそれをUploadLayerPartと呼んでいます。クライアントは潜在的に10ギガバイトのレイヤーを送信しようとしています。Dockerはこのアップロードを使用して、私たちのプロキシサービスへのURLにPATCHリクエストを開始します。これは10ギガバイトすべてを私たちにストリーミングする単一のHTTP接続です。プロキシとフロントエンドの間で、私たちは実際にそれを通常約20メガバイトずつのパートに分割します。プロキシサービスはDockerへの接続を開いたまま保持し、20メガバイトのチャンクでバイトを送信します。実際には仕様に従って順番に行います。つまり、各パートをフロントエンドに順次送信しています。

Thumbnail 780

ここからダイジェストについて説明します。ダイジェストは暗号化ハッシュで、通常はSHA-256です。スライドの下部にあるように、このダイジェストを計算します。これにより、受信したバイトがクライアントの意図したものであることを確認できます。クライアントは自分側で計算し、後でマニフェストでダイジェストを送信しますが、レイヤーデータを受信している間に、私たちはダイジェストを計算する必要があります。各パートごとにこのダイジェスト計算を行っているため、部分的な計算になります。最初のパートしか受信していないので、まだ完全なダイジェストは分かりません。その後、2番目のパートを受信します。そのため、その進捗状況をDynamoDBに保存する必要があります。これはクラスター内で実行されており、時には300台以上のホストで動作することもあります。そのため、リクエストが各ホスト間を行き来する可能性があります。進捗状況を保存し、取り出して、すべてのパートを受信するまで計算を続け、最終的なダイジェストを知ることができます。

Thumbnail 840

すべてのパートを処理したら、それらをS3にもアップロードします。S3は効率的なので、BLOBストレージとして使用しています。これがpullプロセス中にどのように有益かについては後ほど説明します。現時点では、すべてのバイトをS3に置き、レイヤーを作成したことをクライアントに知らせます。仕様には他の部分もありますが、ここでは触れません。中断された場合、クライアントは再開できます。望めば最初からやり直すこともできます。再開にはさまざまな方法がありますが、この説明では簡単に次のステップに進みます。

Thumbnail 870

Thumbnail 890

すべてが順調に進んだと仮定すると、データは手に入りましたが、実際にはまだ完了したかどうかわかりません。次のステップは、クライアントが完了したことを知らせることです。すべてのバイトを送信し、再開してさらに送信する意図がないことを伝えます。これはCompleteLayerUpload APIコールを通じて行われ、これも同様にフローを通過します。POSTリクエストがAPI リクエストに変換され、S3まで到達します。同様のプロトコルを使用して、S3に完了を伝えます。S3がOCI仕様と多くの点で似ているのは便利です。そのため、S3はOCI仕様の動作方法に非常に近いマッピングができるので、私たちにとって優れたデータストアとなっています。

Thumbnail 930

OCI仕様の動作という文脈では、この一連の流れがイメージの各レイヤーに対して実行される必要があることを考慮する必要があります。クライアントが並行して送信することを選択した場合は、同時に行われる可能性もあります。すべてのレイヤーを取得しても、イメージはまだ完成していません。クライアントはマニフェストを送信する必要があります。前のスライドで見たように、マニフェストはこのような形です。このデータの一部は、私たちがメタデータと呼んでいるものです。タグからダイジェストへのマッピングや、S3内のレイヤーの場所などは、DynamoDBに保存されます。これにより、pullやその他の操作中にイメージを素早く取得できます。

Thumbnail 960

このプロセスは比較的単純です。クライアントは、マニフェストURIにPUTリクエストを使用してマニフェストをプッシュします。これを私たちはPutImageと呼んでいます。PutImageは基本的に、DynamoDBとS3の両方に完全なオブジェクトができたことを伝えます。先ほど述べたように、JSONブロブをS3に保存し、これでコンテナイメージが完成します。201ステータスコードを返すことで、Dockerクライアントにイメージが作成され、提供準備ができたことを伝えます。

Amazon ECRのプル操作とレプリケーション機能

では、Rafaに戻しましょう。

Thumbnail 1010

Mikeと私がこのスライドを準備していた際に、非常に明確になったのは、私たちのプレゼンテーションのテーマが「すべては見た目よりも複雑である」ということです。この複雑さは、Mikeが数字で示したように規模によるものですが、また、これらのプロセスがプッシュとプルを超えて、見た目以上に複雑であるためでもあります。次のスライドは興味深い例です。なぜなら、本質的に空白で、 ECRからECSへの移動を示しているだけだからです。これを行う理由は、ポーカープレイボットに戻ると、イメージをECRに取り込むことができたので、今度はそれをデプロイする必要があるからです。

Thumbnail 1030

Thumbnail 1040

ECRからECSにイメージを取得するために、タスク定義を作成します。そこにイメージURLを含めます。下半分で見られるように。また、ECSがアクセスできるように権限を管理する必要もあります。しかし、これは単に機能します。私たちのイメージは現在実行中で、私がトリガーをかけ、そして実際のビットをスケールします。 ECSが必要とするとき、私の介入なしにイメージを取得できることがわかります。

Thumbnail 1050

これもまた、一見シンプルなプロセスです。ここで興味深い点は、以前にプロキシを通じて作業すると話した場合とは異なり、レイヤーデータ自体がECRを通過さえしないことです。それは直接顧客に、この場合はECSのランタイムであるcontainerdに流れます。したがって、ここでもまた、我々が提供するスケールとワークロードのタイプをサポートするために実装する必要がある、通常の操作モードとは異なるひねりがあります。

Thumbnail 1100

さて、もう少し数字について話しましょう。先ほどプッシュについて見ましたが、それは2億5000万でした。プルについては、現在は数十億の規模です。プルトラフィックは、プッシュ側で受け取る量の約200倍です。通常、イメージは一度プッシュされますが、大規模なフリートや大きなECSまたはEKSクラスターで、各ノードがイメージのコピーを取得する可能性があります。これを分かりやすく言うと、私がスライドを変更するたびに、ECRはグローバルで150万のイメージを提供しています。これが、15秒ごとに提供しているイメージの数です。

Thumbnail 1140

プッシュと同様に、プルにも複数のステップがありますが、少しシンプルで3つのステップしかありません。順序が逆になっていることに気づくでしょう。まずマニフェストを取得し、次にレイヤーを取得します。ただし、Docker pullと言っても実際には3つのステップがあり、さらに各レイヤーステップがレイヤーごとに発生する可能性があるため、さらに多くのステップになる可能性があります。

Thumbnail 1160

マニフェストを取得する最初のステップは、プッシュと同様に、すべてがGET側にあります。つまり、マニフェストにGETリクエストを行います。タグを使用してタグで検索することもできますし、digestを知っている場合はそれを使用することもできます。タグは一般的に何であるかわかるため、ユーザーフレンドリーですが、digestは長い文字列です。ECRはどちらの方法でも検索を行い、DynamoDBテーブルで見つけます。マニフェストなので、S3に行ってそのJSONブロブを取得し、Dockerに返します。DockerまたはRafaの場合はcontainerdが、マニフェストを読み取り、次のステップでECRに要求する必要があるすべてのレイヤーを知ることができます。OCIはこの次のステップを「get blob by digest」と呼んでいます。

Thumbnail 1210

このステップでは、Dockerがマニフェストを持っており、そこにdigestが含まれているため、digestを取り出してこのエンドポイントに要求を行います。この時点では実際にバイトを取得するわけではなく、基本的にS3に保存した場所のレイヤーURLを取得します。プッシュ時にS3に保存し、その場所がDynamoDBに保存されています。ここではS3とあまり相互作用しないことがわかります。DynamoDBにレイヤーの場所があり、私たちが行っているのは、この場合containerdであるクライアントにリダイレクトを送り返し、「私たちから取得するのではなく、S3から取得してください。ここで見つけることができます」と伝えることです。

これにより、多くのデータをオフロードできます。データが私たちのサービスを通過する必要がありません。S3はある意味でこのために作られています。そのため、そのブロブデータのダウンロードをS3にオフロードします。ここでもう一つ興味深いのは、これが私たちのS3バケットであるため、世界中にアクセスを許可することはできないということです。そこで、URLを事前に署名します。つまり、DockerクライアントはアクセスできるURLを取得しますが、それは短期間で有効期限が切れ、そのイメージに対してのみ有効です。ここでフロントエンドサービスとメタデータサービスの役割の一部は、そのURLを生成し、リダイレクトとしてクライアントに送り返すことです。

Thumbnail 1290

クライアントにURLを送り返し、「ここに行って取得してください」と伝えます。そしてクライアントがそれを行うと、S3に直接アクセスします。私たちが持っているすべてのサービスをスキップします。S3に直接アクセスし、基本的には単純なcurlや単純なHTTP GETでレイヤーデータを取得できるので、このように動作する独自のクライアントを構築し、コマンドラインからcurlなどを使用して直接ダウンロードすることもできます。そして、再度、私たちがこれを行う理由は、先ほど言及したように、月間約700億回のプルがありますが、これは Amazon ECR から3エクサバイト以上のデータがプルされていることを意味します。

Thumbnail 1330

ECRから1ヶ月間に提供されるデータ量は、このようになります。これは、ECRが世界中で1時間に約4ペタバイトのデータを提供していることを意味します。ここに面白い比較を示しましたが、これは約150個の米国議会図書館分のデータ量に相当します。ECRが1ヶ月間に提供するコンテナイメージデータの量がこれほど膨大なのです。

Amazon ECRのスキャン機能:ベーシックとエンハンスドの比較

Thumbnail 1370

さて、プッシュとプルについて話しましたが、これらは基本的に我々が行う必要がある2つの主要な操作です。しかし、「実際にはもっと複雑だ」というテーマに沿って、ストレージでできることはそれ以外にもあります。ここからは、ECRの他の機能がどのように動作し、裏側でどのようにアーキテクチャが組まれているかについて少し話していきましょう。まず、こんな状況を考えてみましょう:私たちのイメージは非常に好評で、人々は私たちのbotで遊ぶのを楽しんでいます。そして、他のリージョンにデプロイする人々が出てきました。これは必ずしも良いことではありません。レイテンシーの問題が生じるからです。また、リージョン間のデータ転送にもコストがかかり、プル操作が増えるとその費用も積み重なっていきます。

Thumbnail 1390

Thumbnail 1410

Thumbnail 1420

そこで、この問題を回避するメカニズムを見つける必要があります。それを実現するのがレプリケーションです。レプリケーションを使えば、特定のECRリポジトリを選択し、そこにプッシュされたすべてのイメージを他のアカウントや他のリージョンのリポジトリに自動的にレプリケートするよう指示することができます。ECRでの動作を再度説明しますが、ここでは複数の異なるリポジトリを作成しました。このプロジェクトをさらに発展させてきたと想像してみましょう。非常に好調なので、追加のワークロードもあります。すべてをレプリケートしたいわけではありません。そこで、レプリケーションルールで、レプリケートしたい特定のリポジトリと、どのリージョンにレプリケートするかを指定します。これはプレフィックスフィルターを使って動作します。

Thumbnail 1460

基本的に、ECRに対して「CardWhispererで始まるリポジトリの新しいイメージは、これら2つのリージョンに自動的にレプリケートする必要がある」と指示しているのです。ECRは、その時点からそれらのリポジトリ内のすべてのイメージに対してこれを行います。ターゲットリージョンにリポジトリがない場合は、自動的に作成してくれます。つまり、その時点から永続的に行われ、イメージをローカルに持つことができるようになります。これはデモのスクリーンショットです。数日前に私のラップトップで実行したものです。メインリポジトリの1つのリージョンにイメージをプッシュし、8秒後には別のリージョンで利用可能になっているのがわかります。

Thumbnail 1480

ここでもう一度、「裏側では複雑」という考え方に戻りましょう。8秒というのは素晴らしく聞こえますが、常に8秒である必要はないかもしれません。このワークロードを管理する必要があるかもしれません。では、それがどのように機能するか見ていきましょう。

Thumbnail 1500

では、アーキテクチャ図に戻りましょう。ここでも非常に似たようなフローがあります。Rafaのラップトップがproxyにpushしているのがわかりますね。これは通常のpushフローです。これは最初に見た図と同じものです。唯一の違いは、ここで非同期処理を少し追加していることです。pushとpullは同期操作です。

Amazon ECRのLifecycle Policiesによるイメージ管理

つまり、クライアントは私たちの応答を待っています。この場合、クライアントはpushを行っていますが、私たちはそれに基づいてアクションを実行したいのです。クライアントを待たせたまま、グローバルに複製するのは望ましくありません。Rafaが言ったように、この場合は8秒かかりましたが、もっと多くのターゲットや複数の場所があるかもしれません。そのため、これはすべて非同期で行われます。

Thumbnail 1540

その方法は、基本的にAmazon DynamoDBテーブルを監視して、pushが発生したタイミングを知ることです。それによってイベントが生成され、Amazon SQSキューに配置されます。そして、そのキューを使用して、Lambdaを使用したreplication serviceがワーカーを持ち、それらのイベントを取り出し、先ほどRafaが示したルールに基づいて、どこに複製する必要があるかを指示します。この場合、1つのソースリージョンから宛先リージョンに送信しています。

Rafaが言及したように、これは非常に時間のかかるプロセスになる可能性があります。地球規模のリージョン間の物理的なリンクに依存しているからです。そこで、私たちは内部的にAWS Transportという素晴らしいサービスを持っています。これは公開サービスではなく、内部で使用できるものですが、リージョン間でデータを効率的に移動することができます。このAmazon S3バケットからあのS3バケットに移動するように指示すると、チャネル容量に基づいて可能な限り効率的かつ迅速にデータを移動できます。

チャネル容量については後ほど少し触れます。また、ここで起こっているもう1つのことは、blobをリージョン間で移動させることですが、メタデータも移動させる必要があります。その方法として、replication serviceが他のリージョンのフロントエンドを呼び出し、タグやdigestに関する情報、場所、その他すべての情報を伝え、宛先リージョンに保存します。

Thumbnail 1640

レプリケーションには、他にも呼び出せるAPIがあります。SDKとCLIでは、完了したかどうかステータスを確認できます。また、configAPIを使ってルールを変更したり新しいルールを作成したりすることもできます。これはラップトップなどからコンソールやAPIに対して行います。先ほど触れたように、レプリケーションでは複数の場所に送信できます。最大25の異なるターゲットに送信可能です。

パブリックイメージの管理とECR Publicの活用

つまり、1つのソースから25の宛先に送れるということです。これらの宛先は、異なるリージョンの組み合わせでも、同じリージョン内の複数のアカウントでも構いません。様々な理由で複数のアカウントを使用しているけれど、同じイメージをすべてに置きたい場合もあるでしょう。レプリケーションルールでは、最大25までの任意の組み合わせを設定できます。

Thumbnail 1670

先ほどチャネルについて触れました。チャネルとは、あるリージョンと別のリージョンを結ぶリンクのことを指す概念です。Transportが提供するチャネル容量があり、例えば1秒あたり10ギガバイトまで使用できるといった具合です。これはリージョンによって異なる場合があります。あるリージョンから別のリージョンへの容量によって変わってきます。遠く離れたリージョンでは、容量が若干少なくなる可能性があります。

ネットワークイベントによって容量が減少することもあります。物理的なリンクが切断されたり、海底ケーブルが切断されたり、何かが起きて容量を減らさなければならない場合があります。そういった場合、動的に通知を受け、それに応じて私たちは送信量を抑えます。もし抑えなければ、AWS全体のバックボーンに過負荷をかけてしまう可能性があります。抑えなければ、処理できる以上のデータを送信してしまうことになります。そのため、常にやり取りを行い、効率的にバイトを転送できるようにしています。できるだけ早くデータを送りたいのですが、同時にネットワークに負荷をかけすぎないようにしたいのです。

そのため、時には8秒で済むこともあれば、20分かかることもあります。変動があるのです。できるだけ早く到達させようとしていますが、イメージのサイズ自体にも当然依存します。10メガバイトのイメージは10ギガバイトのイメージよりもずっと速く転送できます。そのため、ある程度の変動があります。ただし、ネットワークに負荷をかけすぎないように気をつけていることを覚えておいてください。バランスを取り、スケールさせるために、私たちの側で多くの工夫をしています。

Amazon ECRの数字を見てきましたが、かなり広く使われていることがわかります。私たちの主なモットーの一つは、セキュリティの次に可用性が最優先ということです。もちろんセキュリティは重要ですが、その次に私たちが重視するのは可用性です。システムを稼働させ、目標とする9の数字(99.99...%の可用性)を達成し、データを提供し続けることが大切です。なぜなら、データを提供できなければ、AWS Fargateタスクや Amazon EKS pod、あるいはコンテナを実行できないからです。そのため、ネットワークに悪影響を与えないようにすることで、システムの稼働を確保し、お客様がデータを利用できるようにしています。

さて、もう一つ重要なポイントがあります。ここにいる皆さんにイメージのスキャンの重要性を説明する必要はないと思いますが、もし必要であれば、ぜひイメージをスキャンしてください。イメージにはワークロード全体が含まれているので、脆弱性がないか確認することが重要です。少なくとも、脆弱性の存在を把握し、必要に応じて対処できるようにしておくべきです。Amazon ECRには、ベーシックスキャンとエンハンスドスキャンという2つのスキャンモードがあり、多くの共通点があります。

Thumbnail 1810

両モードの共通点として、イメージがプッシュされたときにスキャンを実行できることや、手動でスキャンをトリガーできることが挙げられます。エンハンスドスキャンには、継続的なスキャン機能という追加機能があります。これにより、本番環境のリポジトリを継続的にスキャンしつつ、開発用リポジトリには適用しないといった柔軟な運用が可能です。また、2つのモードは異なるスキャンエンジンを使用しています。ベーシックスキャンはAWSが管理するオープンソースデータベースであるClair v2を使用し、エンハンスドスキャンはより広範なカバレッジと包括的なソースを持つAWSの別サービスであるAmazon Inspectorを利用しています。

Thumbnail 1840

スキャンの設定にはコンソール画面が用意されており、APIを通じても設定が可能です。レプリケーションと同様に、スキャンするリポジトリを決定するルールを作成できます。例えば、本番環境で重要な「CardWhisperer」を含むリポジトリは継続的にスキャンし、その他のリポジトリはプッシュ時にのみスキャンするといった設定が可能です。これにより、新しいイメージがアップロードされた際に監視することができます。

Thumbnail 1870

Thumbnail 1880

スキャン後は、結果を確認し、必要な対応を取ることができます。スキャンのプロセスは、先ほどMikeが説明したレプリケーションの流れと似ています。リポジトリにイメージをプッシュすると、一連のイベントが発生します。Lambdaで構築されたスキャンサービスが新しいイメージのプッシュイベントを待機し、そのサービスがスキャンを適切なエンジン(ClairエンジンまたはAmazon Inspector)に振り分けます。

Thumbnail 1900

Enhanced modeでの継続的スキャンでは、定期的に新しいスキャンをトリガーするためにInspectorに依存しています。どちらの場合も、スキャン結果は私たちのmetadata serviceにリダイレクトされます。そこから、顧客はスキャン結果を確認し、Inspectorを使用している場合はアクションを取ったり、結果を分析したりすることができます。このシステムにより、顧客は必要に応じてスキャン設定を変更することもできます。

Thumbnail 1970

次に、Lifecycle Policiesと呼ばれる別の機能について説明しましょう。CardWhispererリポジトリでは、頻繁なパイプラインの実行とバージョニングにより、何千ものイメージが存在する可能性があります。 ECRのLifecycle Policiesを使用すると、これらのイメージをクリーンアップするためのルールを定義できます。シンプルなポリシーとしては、「最後にプッシュされてから30日以上経過したイメージを削除する」というものがあります。ただし、このような単純なポリシーは使用状況に関係なくイメージを削除してしまうため、注意が必要です。

Thumbnail 2010

Lifecycle Policyのプロセスは非同期ですが、プッシュイベントに基づいているわけではありません。バックグラウンドでは、これらのポリシーを実装するためのジョブを継続的に実行しています。Lifecycle policyサービスがポリシーのリストを調べ、それをワーカーに送信します。ワーカーはリポジトリを確認し、イメージの経過時間をチェックし、削除候補を見つけ、効率性のために通常5〜10イメージずつバッチで削除イメージ機能を呼び出します。

興味深いことに、イメージを削除しても、即座に完全に削除されるわけではありません。私たちはmark-sweepパターンを使用し、まず削除のマークを付けます。あなたには削除されたように見えますが、実際にはS3やDynamoDBから物理的に削除されていません。後でバックグラウンドプロセスがこれらのマークされたイメージをクリーンアップします。これは、Lifecycle Policiesによってトリガーされた削除や手動のバッチ削除イメージ呼び出しを含むすべての削除に適用されます。削除のマークが付けられた瞬間からイメージの計測を停止するので、この猶予期間中は課金されません。

Thumbnail 2100

規模感をお伝えすると、Lifecycle Policiesだけで毎月6000万以上のイメージを期限切れにしています。これには直接の削除は含まれていません。このカンファレンス週間中だけでも、約1500万のイメージが期限切れになります。このプロセスはストレージコストの節約に役立ち、脆弱性を含む可能性のある古いイメージを削除します。

これらのライフサイクルポリシーには、お客様にとって2つの主なメリットがあります。1つ目は、明らかにクリーンアップすればするほど、必要なストレージが少なくなり、ECRの使用コストが下がることです。1ギガバイトのイメージが7,000個あれば、7,000ギガバイトになり、これは保持するにはかなりの量のデータです。そのため、期限切れにすれば、その分の料金は発生しません。また、Rafaが言及したように、イメージが古くなるにつれて脆弱性が蓄積される可能性があります。誤って古いイメージを実行して、脆弱性にさらされるのは避けたいものです。そのため、古いイメージを期限切れにすることで、脆弱性を含む可能性のあるイメージを実行しないようにできます。これらがライフサイクルポリシーのメリットの一部です。

Thumbnail 2160

Amazon ECRのPull-through cache機能とその仕組み

私の心に近いもう1つのトピックがあります。私はECR Publicに多くの時間を費やしていますが、これはパブリックイメージに関するものです。お客様と話をする中で、私たちの取り組みに満足しています。しかし一般的に、スライドの上部に書かれているルールが適用されます:パブリックイメージの所有権を取得してください。プレゼンテーションの2枚目のスライドに戻ると、私のDockerfileを示しましたが、それはこのようなものでした。ECR Publicからプルしています。ここで言いたいのは、ECR Publicは利用可能で、安全で、スケーラブルですが、それでもパブリックレジストリから直接パイプラインやワークロードにプルしないでください。まずプライベートにプルしてください。

Thumbnail 2200

イメージがプライベートレジストリに入ったら、それに対してさまざまなことができます。スキャンしたり、一定期間後に期限切れにしたり、必要に応じてレプリケートしたりできます。そして、上流のコンテンツが利用できなくなったり、イメージの作成者や所有者が望ましくない変更を加えたりしても、上流への依存を避けることができます。

Thumbnail 2220

そのために、Pull-through cacheという機能があります。Pull-through cacheを使うと、上流のレジストリ全体を、ECRレジストリ内の特定のnamespaceにマッピングすることができます。つまり、そのnamespaceを使って画像をpullしようとすると、ECRが上流から画像を取得し、ローカルにコピーを保存して管理してくれるのです。

Thumbnail 2240

ここでそれを確認できます。Dockerfileを変更しましたが、内容は全く同じで、'from'の行だけが異なります。まだ'five from 3.12'で終わっていますが、他の部分は全て異なります。しかし、プロセスは全く同じです。ビルドも同じで、Dockerは違いを認識しません。全てECRが裏で処理しているのです。

Thumbnail 2260

では、これはどのように機能するのでしょうか?ここでもまた、小さな改良が加えられています。私がpullを行い、proxyを呼び出し、proxyがfront endを呼び出します。しかし、この時点でmirror in serviceが介入し、「待ってください、これはmirrorネームスペースの1つを使用しているので、確認する必要があります」と言います。まず、イメージがECRで利用可能かどうかを評価します。次に、イメージが最後にpullされてから長時間経過しているかどうかも評価します。これは様々ですが、最低でも24時間です。これらのどちらも該当しない場合、上流のレジストリでイメージを見つけ、そこにリダイレクトし、ローカルコピーを保持するためにイメージをpullします。該当する場合は、ECRから提供されます。このように、レイテンシーを最小限に抑えようとしています。上流のイメージがECRに行き、そしてあなたに届くのを待たせることはしません。しかし、一度それを行えば、イメージはローカルで利用可能になり、再びスキャンやレプリケーションなど、必要な処理を行うことができます。

Thumbnail 2320

Amazon ECRの最新機能と今後の開発計画

そして、これでほぼ終わりです。最後のスライドでは、最近の機能と今後予定されている機能について話します。ECRが行う多くのことについて言及しましたが、最近変更されているいくつかの点についてお話ししたいと思います。Pull-through cacheから始めましょう。Pull-through cacheは、基本的に先週まで、上流のレジストリとしてECR PublicとQuay.IOをサポートしていました。現在、認証が必要な追加のレジストリもサポートしています。これには、Docker Hub、GitHub Container Registry、Azure Container Registryが含まれます。つまり、Secrets Managerに認証情報を保存しておけば、ECRがあなたに代わって上流のレジストリに認証し、それらのイメージをpullすることができます。

Thumbnail 2390

スキャニングに関しては、来年を通じて基本的なスキャニングの能力を強化し、より多くのオペレーティングシステムをカバーし、お客様により多くのサポートを提供する予定です。最近では、Amazon Linuxオペレーティングシステムの1つであるAL 2023のサポートも基本的なスキャニングに追加しました。Lifecycle Policiesについては、その核心は削除するものを正確に制御することです。お客様が非常に細かくイメージの削除場所を選択できるようにしたいと考えています。意図しないものをpushしてしまうと、悪い経験になってしまいます。 そこで、いくつかの機能を追加しています。ワイルドカードサポートが間もなく実装され、お客様はライフサイクルポリシーに一致するタグ、つまり削除されるイメージをより慎重に選択できるようになります。また、最後に記録されたpull時間セレクターにも取り組んでいます。これは、使用中のイメージを誤って削除しないという追加のセキュリティレベル、少なくとも確信を得られるため、お客様からよく要望が上がる機能です。

開発中の興味深いコアレジストリ機能が多数あります。

Repository Creation Templatesはそのような機能の1つです。先ほど述べたように、イメージをクロスリージョンでレプリケートする場合や、Pull-through cacheを通じてイメージをプルする場合、ローカルリポジトリが存在しない場合は自動的に作成されます。これにより、他のリポジトリとは異なる設定のリポジトリが作成される可能性があります。暗号化や希望するタイプのスキャンが設定されていない可能性があります。Repository Creation Templatesを使用すると、新しいリポジトリのデフォルト設定を構成できます。Repository Creation Templatesは現在プレビュー段階で、認証されたPull-through cacheで機能していますが、今後機能を拡張してレプリケーションにも追加する予定です。

また、OCI reference typesにも取り組んでいます。ECRはOCI仕様を実装しており、OCIカウンシルはバージョン1.1の仕様に取り組んでいます。これにはreference typesが含まれています。私たちは彼らと協力し、進捗を追跡しています。彼らの仕様が最終決定されリリースされ次第、ECRでも短期間のうちに利用可能にする予定です。開発中のもう1つの機能は、Create on pushです。これは顧客からよく要望があり、他のレジストリでもサポートされている機能です。存在しないリポジトリにイメージをプッシュしようとすると、自動的に作成されます。

パフォーマンスも重点的に取り組んでいる分野です。顧客と話すたびに、パフォーマンスについて尋ね、ECRに満足しているかを確認しています。多くの回答は肯定的ですが、誰もがより速くなることを望んでいます。最近、プルのパフォーマンスを2倍に向上させる変更をリリースしました。来年のロードマップには、プッシュとプルのパフォーマンスを2〜4倍向上させる改善が複数含まれています。これらを実現するために、内部的な変更を行っています。

最後に、レプリケーションの改善に取り組んでいます。Lifecycle Policiesと同様に、レプリケーションのワイルドカードサポートがロードマップに含まれています。顧客がレプリケートしたいものを慎重に選択できるよう、より細かな制御方法を提供することが重要です。また、インクルージョン/エクスクルージョンリストの開発も進めています。これは、レプリケートしたいリポジトリがあるものの、一部のタグはレプリケートしたくないという特定のユースケースに対応します。例えば、「latest」タグは単にプッシュするために使用する変動するタグかもしれませんが、デプロイメントパイプラインには含まれないものかもしれません。そのため、これをレプリケートしたくない場合があります。これにより、コストを節約し、ダウンストリームのリポジトリでの管理が不要になります。

プレゼンテーションの締めくくりと質疑応答の案内

これらが私たちのロードマップの主な項目ですが、すべてではありません。ロードマップは流動的であり、顧客のフィードバックに基づいて変更を続けます。現在はこれらの機能に焦点を当てており、来年にはこの一部を提供する予定です。以上でプレゼンテーションは終わりです。質問の時間がありますので、声を出して質問するか、個別に他の質問をしていただくことができます。


※ こちらの記事は Amazon Bedrock を様々なタスクで利用することで全て自動で作成しています。
※ どこかの機会で記事作成の試行錯誤についても記事化する予定ですが、直近技術的な部分でご興味がある場合はTwitterの方にDMください。

Discussion