電通総研のブログ - TECH PLAY

TECH PLAY

電通総研

電通総研 の技術ブログ

849

はじめまして、ISID エンタープライズ xRセンター Lumiscaphe担当の加納です。 ISIDアドベントカレンダー も4日目ですね! 今回はLumiscaphe系ソフトを用いたワークフローの中核をなす、Patchwork3D…通称「P3D」の紹介をしたいと思います。 Lumiscaphe担当の私はもともと、 プロダクトデザイナー として3D系のソフトを使用していました。それを踏まえて、実際に使用する中でどのように役立ちそうか?どこが良いのか?等をお伝えします。 リアルタイムレンダラーとは? P3Dの特徴 UV展開が得意 布表現が得意 塗装表現が得意 誤操作を起こしにくい データの活用 AccelVR Lumis3D 最後に リアルタイムレンダラーとは? レンダラーは聞いたことがあるけど、「リアルタイムレンダラー」は耳慣れない。という方もいらっしゃるのではないでしょうか? 3Dデータに質感を付け写実的な形に整えるのがレンダラーの役割ですが、そのレンダラーにもいくつか種類があります。 1つは建築などで一般的に用いられる レイトレースレンダラー 。光の反射回数などを指定し、空気感や複雑な反射に長けたタイプのレンダラーです。 特徴は光の反射を追って画面を作るため、じっくり時間をかけてフォトリアルな画面を作っていくことにあります。反面、きちっと1枚の画を作るのに時間がかかると言い換えることも出来ます。上のGIF動画で見比べると分かりやすいですね。 もう一つが リアルタイムレンダラー です。こちらは文字通りリアルタイム性を重視しており、フォトリアルさを追求しようとするとレイトレース以上に技量を求められます。しかしその分リアルタイムで反射を演算できるため、反射を見て面の繋がりを確認する…といったシビアな評価にも使うことが出来ます。 リアルタイムレンダラーは VR 表示可能なソフトが多く、このP3DもAccelVRというソフトを用いた VR 表示が可能です。 製造業デザインにおけるデジタル モックアップ として使用できるタイプのレンダラー と言えるでしょう。 なお最近はレイトレース系としてリアルタイムレイトレースレンダラーなんてものもありますが、反射を見て面の繋がりを確認する用途には少し厳しいだろうというのが個人的な感想です。それぞれ違う用途があるというわけですね。 P3Dの特徴 UV展開が得意 例えばチェック柄のついた布にクシャッとしわを寄せたとき、チェック柄はしわに沿って歪みますよね。3D上でそれを再現するために必要な処理が「UV展開」なのですが、この処理が実はとても厄介です。 それを簡単に行えるのがP3Dの強みです。 布表現が得意 布のように柔らかく形が変わりやすいもののUV展開を容易に行える上、布のマテリアル表現が得意です。 薄い層を重ねるようにして効果を重ねてマテリアルを作る「マルチレイヤーマテリアル」が多彩な表現を可能にし、近付いてもリアルな表現を維持します。 VR でこの質感を体感できると思うとワクワクしませんか? 塗装表現が得意 実際に塗装をする際、吹き付け1回で塗装完了!ということはありませんよね。 通常は、色のついた塗料を吹き付けた後で重ねてクリアコートを吹き付ける…といったように、2工程以上に分かれているものではないでしょうか。P3Dでは前述の「マルチレイヤーマテリアル」が重ね表現を可能にし、現実と同様の工程を踏んでの塗装表現を可能にしています。 レイヤーなしで同様の表現をすることももちろんできるのでしょうが、レイヤーを用いることでより編集が簡単になり、ひいては作成時間の短縮にも繋がります。 青っぽい要素を足したいけど、足す前との比較をしながら検討したい!といった場合にも、マテリアル自体を増やさずにレイヤーのON・OFFで比較出来ますしね。 誤操作を起こしにくい 他のレンダラーを触っているときに、テクスチャを掴んで動かしたいのに、うっかり サーフェス を触ってしまった… サーフェス が動かないな?と思いながら触っていたらテクスチャを動かしており、Ctrl+Zで戻せる回数を超えてしまった… なんてこと、ありませんか?恥ずかしながら私はまあまあ心当たりがあるのですが、P3Dでは起きません。 なぜならP3Dでは、形状・ サーフェス のレイヤー構造を編集する「Shaper」画面と、テクスチャなど見た目を編集する「Matter」画面に分かれているからなのです。 画面の作りに関して個人的な意見を付け加えさせていただくと、 Adobe ライクなUIで、特にデザイナーの方にやさしい構造だと感じています。多機能でありつつも、3Dを全く触ったことがなくても使える作りのレンダラーだと言えるでしょう。 データの活用 冒頭で「Lumiscaphe系ソフトを用いたワークフロー」と書いた通り、P3Dはデータの活用先が多彩です。 VR 表示で見られるほか、 GPU を積まないPCや タブレット 、 スマートフォン などで レンダリング を表示できます。簡単にご紹介いたしますね。 AccelVR レンダラーの種類紹介の部分で少し触れたソフトです。 VR 表示を可能にする関連ソフトで、P3Dと変わらない品質での VR 表現ができます。P3Dから専用形式に書き出して読み込むことで、 HMD (Head Mounted Display…つまり頭に被るタイプの VR )のほか、スクリーンに囲まれた部屋へ人間が入るタイプの VR 「CAVE」などでも表示可能です。 専用形式に書き出して別データで持つ必要があるというのは面倒に思われがちですが、実は管理の上でメリットがあります。 1つは書き出した時点での見え方をキープできるということ。 ちょっと実体験が混ざってしまうのですが、編集中のデータを途中報告した際に「〇日に見たデータってどんなんだっけ?」と言われることがあるんですよね。編集中データだから、もう更新してしまっているのに…。 そんな時に、別ファイル書き出しのワークフローが挟まっていると救われることがあります。 もう1つは、データの独り歩きを防ぐことが出来ます。 AccelVRのファイル形式はAccelVRと後述のLumis3Dでしか使えない上、原則として編集可能データへの戻しが不可能です。CADオプションを入れていればAccelVR内で描画した形状のみCADへの戻しが可能ですが、データの流出防止に便利なのは間違いありません。 Lumis3D 「サーバーサイド レンダリング 」という概念をご存じでしょうか。 通常、 レンダリング というとPCの処理能力が高くないとできないし、開始すると冷却ファンがうなりだすものだ…というイメージはありませんか? サーバーサイド レンダリング では、 レンダリング 処理を遠隔地に置いたサーバーで行います。処理速度はサーバーのスペックに依存しますが、重たい処理をPC内で行わないため、 GPU を搭載しないPCでも問題なく表示できます。もちろん タブレット での表示も可能です。 用途としては、一般PCでのデザイン確認(検討や承認過程など)・販 売店 でのデザインバリエーション提示・ ECサイト へ組み込んでユーザー自身にデザインバリエーションを確認してもらう、などが想定されています。Lumis3D内でP3DファイルやPDFを開くことができるので、デザイン確認の可能なオンラインデータ共有ツールとしても使用できます。 最後に いかがでしょうか。LumiscapheやP3D、興味を持っていただけましたか? ISIDにて私の属するExRCでは、様々なソフトやサービス・最新機器のご紹介を行っております。まだまだ新しい組織でコンテンツも増やしているところですが、P3Dの詳しい使い方講座なども行っておりますのでぜひ一度覗きに来てみてください。 最後までお読みいただきありがとうございました。 まだまだ続く アドベントカレンダー 、5日の更新をお楽しみに! 執筆: @kano.nanami 、レビュー: @higa ( Shodo で執筆されました )
これは 電通国際情報サービス アドベントカレンダー の3日目の記事です。 こんにちは。 電通国際情報サービス (ISID) イノベーション ラボの比嘉です。 うちの会社が、テックブログを始めるということなので、僕もブログを再開します。以前は個人のブログだったので、技術以外のエントリもいろいろありましたが、今後は会社のテックブログとしてやっていきます。 ブログでは基本的にEthereumのプログラミングについて書いていく予定です。僕自身、Ethereumについてほとんど知らないので、学んでいく過程をブログに書いていきます。また、できるだけ意図的にエラーを起こし、それを考察していこうと思います。失敗から学べることは多いからね。 Gethとは Gethのインストール MacへのGethのインストール WindowsへのGethのインストール プライベートネットワークへの接続 データディレクトリの作成 Genesisブロックの作成 Gethの起動 Geth attachサブコマンド etherの採掘 etherの送金 Gethとは Ethereumを利用する場合、まずはEthereumのネットワークに参加する必要があります。ネットワークへの参加には、Ethereumクライアントを使います。 Ethereumクライアントの中で公式に推奨されているクライアントがGethです。Gethは プログラミング言語 Goにより実装された CUI クライアントです。Gethを使うことで次のようなことができます。 etherの採掘 etherの送金 スマート・ コントラ クトの生成 Gethのインストール Gethのインストールの公式サイトはこちら Mac と Windows については、載せておきます。 Mac しか試していません。ごめんなさい。 Mac へのGethのインストール Homebrewを使うのが簡単です。Homebrewをインストールしてから次のコマンドを実行してください。 $ brew tap ethereum/ethereum $ brew install ethereum Windows へのGethのインストール インストーラ ーをダウンロードして実行しましょう。 ダウンロードサイトはこちら プライベートネットワークへの接続 Ethereum 本番環境ネットワークにいきなり接続するのは怖いですよね。まだ、Ethereumのこともよく知らないし。まずは、自分だけの閉じたネットワーク(プライベートネットワーク)を作成してそこに接続しましょう。 データ ディレクト リの作成 データを格納する ディレクト リを作って、そこに移動します。例として、private_netという ディレクト リを作ります。 $ mkdir private_net $ cd private_net Genesis ブロックの作成 ブロックチェーン 上の最初のブロックを Genesis ブロックと言います。プライベートネットワークでは、 Genesis ブロックは自分で作成します。 Genesis ブロックを作成するための元情報として、 Genesis ファイルを作成しましょう。 公式サイトのgenesis.json の中身をコピーして、chainIdを任意の正数にします。今回は、1203にします。 いくつかのchainIdは既に使われているので避けましょう。 { " config ": { " chainId ": 1203 , " homesteadBlock ": 0 , " eip150Block ": 0 , " eip155Block ": 0 , " eip158Block ": 0 , " byzantiumBlock ": 0 , " constantinopleBlock ": 0 , " petersburgBlock ": 0 , " istanbulBlock ": 0 , " berlinBlock ": 0 , " londonBlock ": 0 } , " alloc ": {} , " coinbase ": " 0x0000000000000000000000000000000000000000 ", " difficulty ": " 0x20000 ", " extraData ": "", " gasLimit ": " 0x2fefd8 ", " nonce ": " 0x0000000000000042 ", " mixhash ": " 0x0000000000000000000000000000000000000000000000000000000000000000 ", " parentHash ": " 0x0000000000000000000000000000000000000000000000000000000000000000 ", " timestamp ": " 0x00 " } 作成した genesis . json は、データ ディレクト リに保存します。 次のコマンドで、 Genesis ブロックを作成します。 $ geth init genesis.json INFO [11-29|16:32:24.485] Maximum peer count ETH=50 LES=0 total=50 INFO [11-29|16:32:24.499] Set global gas cap cap=25000000 INFO [11-29|16:32:24.499] Allocated cache and file handles database=/Users/higayasuo/Library/Ethereum/geth/chaindata cache=16.00MiB handles=16 INFO [11-29|16:32:24.576] Persisted trie from memory database nodes=0 size=0.00B time="6.87µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B Fatal: Failed to write genesis block: database contains incompatible genesis (have d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3, new 5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0) ログを見ると、 Failed to write genesis block: と処理に失敗しています。--datadirオプションでデータのための ディレクト リを指定していないので、ライブラリがインストールされている方の ディレクト リに書き込んで失敗しているのでしょう。 今度は、--datadirを指定して、geth initを呼び出します。--datadirで指定した ディレクト リにgethとkeystoreの ディレクト リがあれば、初期化はうまくいっています。 $ geth --datadir . init genesis.json 実は、gethを開発モード(--dev)で起動するといろんなものをあらかじめ準備してくれるので、すぐに送金などを試すことができます。しかし、Ethereumをきちんと理解するには、最初は自分で全部やってみるのが良いと思います。 Gethの起動 先ほど、指定したchainIdをnetworkidに指定して、Gethを起動します。 Gethはデフォルトで自動的に同じネットワークIDのノードを探し接続を試みます。今回、そのような動作は必要ありません。--nodiscoverを指定し無効にします。 $ geth --networkid 1203 --nodiscover ログを見てください。 IPC endpoint opened のログが下のほうにあるはずです。 INFO [11-29|17:03:04.231] IPC endpoint opened url=/Users/higayasuo/Library/Ethereum/geth.ipc geth.ipcが先ほど作成したデータ ディレクト リ(private_net)ではなく、$HOME/Library/Ethereumに作成されています。これは、--datadirオプションを指定し忘れたためです。CTRL-Cでプロセスをいったん止め、次のコマンドで再度起動しましょう。 $ geth --networkid 1203 --nodiscover --datadir . IPC endpoint opened のログを探して、データ ディレクト リ(private_net)にgeth.ipcが作成されたことを確認してください。geth.ipcは、今起動したプロセスに、別のGethがコンソールでアクセスするときに使います。 Gethのconsoleサブコマンドでコンソールとして起動することもできますが、お勧めしません。なぜかというと、Gethのいろんなログがコンソールに表示され使いにくいためです。また、コンソールを終了させるとGeth自身も終了してしまいます。Gethはconsoleなしで起動し、Gethを対話的に操作したい時に別のターミナル(プロセス)から geth attach を使って既に起動したGethのコンソールを立ち上げるのがおすすめです。 Geth attachサブコマンド 別のターミナルを立ち上げ、データ ディレクト リ(private_net)に移動しましょう。Gethのattachサブコマンドで、既に立ち上げたGethプロセスのコンソールを立ち上げることができます。 $ geth attach geth.ipc Welcome to the Geth JavaScript console! instance: Geth/v1.9.25-stable/darwin-amd64/go1.15.6 at block: 0 (Thu Jan 01 1970 09:00:00 GMT+0900 (JST)) datadir: /Users/higayasuo/dev/ethereum/private_net5 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d > Gethは プログラミング言語 Goで作られた CUI ですが、コンソールは JavaScript で出来ているようですね。 geth attach に--datadirをつけ忘れているのではと思う方もいらっしゃるかもしれません。これまでつけ忘れていつも失敗していましたからね。でも、たぶん大丈夫。 geth attach は既に起動したGethプロセスのコンソールなので、そちらに--datadirが付いていれば特に問題はないはず。 etherの採掘 何をやるにしてもまずは元手(ether)が必要です。採掘をしてみましょう。まずは、採掘の状態を確認します。 > eth.mining false まだ、採掘は始まっていませんね。minerモジュールを使って採掘を始めましょう。採掘は非同期に行われます。 > miner.start() Error: etherbase missing: etherbase must be explicitly specified at web3.js:6347:37(47) at web3.js:5081:62(37) at <eval>:1:12(3) etherbaseとは採掘の報酬を受け取るアカウントのことです。現在のアカウントの状態を調べてみましょう。 > eth.accounts [] まだ空っぽです。アカウントを作成しましょう。personalモジュールを使います。 > personal.newAccount() Passphrase: Repeat passphrase: "0x29faad1bb68151278c47df617766bf045c9b2b00" 最後に表示された16進数がアカウントのアドレスです。 eth.accounts をチェックしてみましょう。 > eth.accounts ["0x29faad1bb68151278c47df617766bf045c9b2b00"] もう一つ確認しておきたいのが、 eth.coinbase です。coinbaseはetherbaseと同じものです。 > eth.coinbase "0x29faad1bb68151278c47df617766bf045c9b2b00" coinbaseは eth.accounts[0] が自動で設定されます。 miner.setEtherbase() で eth.accounts[0] 以外のアカウントを設定することもできます。試してみましょう。 > personal.newAccount() Passphrase: Repeat passphrase: "0x0577d82aa10fea504320a087f822d8f68899f980" > eth.accounts ["0x29faad1bb68151278c47df617766bf045c9b2b00", "0x0577d82aa10fea504320a087f822d8f68899f980"] > miner.setEtherbase(eth.accounts[1]) true > eth.coinbase "0x0577d82aa10fea504320a087f822d8f68899f980" 二番目のアカウントが eth.coinbase になっていますね。 etherbaseを変更することが確認できたので、元に戻しておきましょう。 > miner.setEtherbase(eth.accounts[0]) true > eth.coinbase "0x29faad1bb68151278c47df617766bf045c9b2b00" etherbaseが設定できたので、採掘を開始しましょうと言いたいところですが、その前にいくつか確認しておきたいことがあります。一つ目は、 eth.blockNumber 。 > eth.blockNumber 0 0といってもブロックがないわけではなく、 Genesis ブロックのblockNumberが0だということです。blockNumberは採掘が進めば増えていきます。ブロックの中身も eth.getBlock() で確認しておきましょう。logsBloomだけ長いの省略しています。 > eth.getBlock(0) { difficulty: 131072, extraData: "0x", gasLimit: 3141592, gasUsed: 0, hash: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0", logsBloom: "0x000000000000000000000000000000省略", miner: "0x0000000000000000000000000000000000000000", mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0000000000000042", number: 0, parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 507, stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", timestamp: 0, totalDifficulty: 131072, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] } これをみても「これが Genesis ブロックなのか」という実感は湧かないと思いますが、こんなものだと思ってください。アカウントの残高も確認しておきましょう。 > eth.getBalance(eth.accounts[0]) 0 > eth.getBalance(eth.accounts[1]) 0 お待たせしました。採掘を開始しましょう。 > miner.start() null > eth.mining true Gethのメインプロセスのログも見てみましょう。モリモリ採掘しているのが分かります。 eth.blockNumber も確認しましょう。 > eth.blockNumber 127 数が増えているので、きちんと採掘されていることが確認できます。アカウントの残高も確認しておきましょう。 > eth.getBalance(eth.accounts[0]) 2.27e+21 > eth.getBalance(eth.accounts[1]) 0 残高の単位はweiといって、1 ether = 1e+18 weiです。今、1 etherは50万くらいなので、 eth.accounts[0] の残高は1億円超えてますね。 etherの送金 etherの送金は、 eth.sendTransaction() を使います。 from に送金元アカウント、 to に送金先アカウント、 value に送金額をweiで指定します。weiよりetherの方が分かりやすいという場合には、 web3.toWei() を使うといいです。次の例では、2 etherをweiに変換しています。 > web3.toWei(2, 'ether') "2000000000000000000" それでは、 eth.accounts[0] から eth.accounts[1] に 2 ether 送金してみましょう。 > tx = {from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(2, 'ether')} { from: "0x29faad1bb68151278c47df617766bf045c9b2b00", to: "0x0577d82aa10fea504320a087f822d8f68899f980", value: "2000000000000000000" } > eth.sendTransaction(tx) Error: authentication needed: password or unlock at web3.js:6347:37(47) at web3.js:5081:62(37) at <eval>:1:20(4) おや、 Error: authentication needed: password or unlock のエラーが出てしまいました。送金元は、アンロックしておく必要があります。アンロックはデフォルトで5分間持続します。アンロック後、5分以上経過した場合、送金したいなら再度アンロックする必要があります。 personal.unlockAccount() の三番目の引数で、アンロックが持続する時間を秒で指定することもできます。二番目の引数はパスワードなので、パスワードが、見えてしまう問題が生じいまいちですね。 > personal.unlockAccount(eth.accounts[0]) Unlock account 0x29faad1bb68151278c47df617766bf045c9b2b00 Passphrase: true > eth.sendTransaction(tx) "0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0" 最後に表示されたのは、 トランザクション IDです。 eth.getTransaction() で中身を確認してみましょう。 > eth.getTransaction("0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0") { blockHash: "0xe510ee2dc063657b6506b85c78ec361c77d6acc4755b664f2b94faee34b934dd", blockNumber: 4702, from: "0x29faad1bb68151278c47df617766bf045c9b2b00", gas: 21000, gasPrice: 1000000000, hash: "0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0", input: "0x", nonce: 0, r: "0x407f7a5709fc864a8d485cde854c880329475539b773d0e429de0e324f0241c9", s: "0x4998ca73871d2a4dd05374abeb7352f9e8a3c48c1bc288cfe9dbca0ba6206300", to: "0x0577d82aa10fea504320a087f822d8f68899f980", transactionIndex: 0, v: "0x989", value: 2000000000000000000 } from, to, value がきちんと設定されていますね。blockHash, blockNumberが設定されているので、この トランザクション は採掘者によって採掘済みであることがわかります。いつまでたっても、blockHash, blockNumberが設定されない場合は、採掘処理が動いていないかもしれません。 eth.mining で確認して false の場合は、 miner.start() させましょう。 送金先の残高が増えていることを確認しましょう。 web3.fromWei() でweiからetherに単位を変換できます。 > web3.fromWei(eth.getBalance(eth.accounts[1]), 'ether') 2 トランザクション を処理するとき、採掘者に支払われる手数料をgas代といいます。 トランザクション 情報にあったgasの値は、gas代ではありません。 トランザクション 処理時におけるgas使用量の最大値を示しています。 それでは、gas代を知るにはどうしたら良いのでしょうか。送金の時に、送金元の残高から、送金額とgas代が引かれます。そこから計算する必要があります。 eth.accounts[0] は、採掘で常に残高が増え続けているので、gas代を計算するのに向いていません。 eth.accounts[1] から eth.accounts[0] に 1 ether 送金してみましょう。 eth.accounts[1] の現在の残高は、 2 ether ですから 1 ether より少なくなった分がgas代になります。 eth.accounts[1] が送金元になるので、アカウントをアンロックしておく必要があります。失敗から学ぶと言っても同じ失敗を繰り返してはダメですよ。 > personal.unlockAccount(eth.accounts[1]) Unlock account 0x0577d82aa10fea504320a087f822d8f68899f980 Passphrase: true > tx2 = {from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(1, 'ether')} { from: "0x0577d82aa10fea504320a087f822d8f68899f980", to: "0x29faad1bb68151278c47df617766bf045c9b2b00", value: "1000000000000000000" } > txId2 = eth.sendTransaction(tx2) "0xf9c427c253bfd6980c236a691393e1cc501b1d3d71deaf5ddcf17f2d3fca206f" > txInfo2 = eth.getTransaction(txId2) { blockHash: "0xbf4c0d1ccc2325ad2ada70148b818724119ba582d4f0f765b3f56edc36cabc4f", blockNumber: 6378, from: "0x0577d82aa10fea504320a087f822d8f68899f980", gas: 21000, gasPrice: 1000000000, hash: "0xf9c427c253bfd6980c236a691393e1cc501b1d3d71deaf5ddcf17f2d3fca206f", input: "0x", nonce: 0, r: "0x2703788aaad9dd2ae6d08802ae555cdbdbdd1a6dfc432aa94fd1ccc5f4efe8a0", s: "0x413213c0447df661f8ef204e21654ff41894d219a3181f17cad30895aea4034e", to: "0x29faad1bb68151278c47df617766bf045c9b2b00", transactionIndex: 0, v: "0x98a", value: 1000000000000000000 } > gasFee2 = web3.toWei(1, 'ether') - eth.getBalance(eth.accounts[1]) 21000000000000 > gas2 = gasFee2 / txInfo2.gasPrice 21000 gasFee2が今回の トランザクション のgas代です。gasPriceは1gasあたりの手数料です。gas代をgasPriceで割ると今回、何gas使用したかが分かります。 今回はここまで。Gethを止めておきましょう。コンソールはCTL-D。メインプロセスはCTL-Cで止めます。 次回は、スマート コントラ クトを扱います。 僕の書いたNFT関連の記事 NFT入門 スマートコントラクト入門 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
これは 電通国際情報サービス アドベントカレンダー の3日目の記事です。 こんにちは。 電通国際情報サービス (ISID) イノベーション ラボの比嘉です。 うちの会社が、テックブログを始めるということなので、僕もブログを再開します。以前は個人のブログだったので、技術以外のエントリもいろいろありましたが、今後は会社のテックブログとしてやっていきます。 ブログでは基本的にEthereumのプログラミングについて書いていく予定です。僕自身、Ethereumについてほとんど知らないので、学んでいく過程をブログに書いていきます。また、できるだけ意図的にエラーを起こし、それを考察していこうと思います。失敗から学べることは多いからね。 Gethとは Gethのインストール MacへのGethのインストール WindowsへのGethのインストール プライベートネットワークへの接続 データディレクトリの作成 Genesisブロックの作成 Gethの起動 Geth attachサブコマンド etherの採掘 etherの送金 Gethとは Ethereumを利用する場合、まずはEthereumのネットワークに参加する必要があります。ネットワークへの参加には、Ethereumクライアントを使います。 Ethereumクライアントの中で公式に推奨されているクライアントがGethです。Gethは プログラミング言語 Goにより実装された CUI クライアントです。Gethを使うことで次のようなことができます。 etherの採掘 etherの送金 スマート・ コントラ クトの生成 Gethのインストール Gethのインストールの公式サイトはこちら Mac と Windows については、載せておきます。 Mac しか試していません。ごめんなさい。 Mac へのGethのインストール Homebrewを使うのが簡単です。Homebrewをインストールしてから次のコマンドを実行してください。 $ brew tap ethereum/ethereum $ brew install ethereum Windows へのGethのインストール インストーラ ーをダウンロードして実行しましょう。 ダウンロードサイトはこちら プライベートネットワークへの接続 Ethereum 本番環境ネットワークにいきなり接続するのは怖いですよね。まだ、Ethereumのこともよく知らないし。まずは、自分だけの閉じたネットワーク(プライベートネットワーク)を作成してそこに接続しましょう。 データ ディレクト リの作成 データを格納する ディレクト リを作って、そこに移動します。例として、private_netという ディレクト リを作ります。 $ mkdir private_net $ cd private_net Genesis ブロックの作成 ブロックチェーン 上の最初のブロックを Genesis ブロックと言います。プライベートネットワークでは、 Genesis ブロックは自分で作成します。 Genesis ブロックを作成するための元情報として、 Genesis ファイルを作成しましょう。 公式サイトのgenesis.json の中身をコピーして、chainIdを任意の正数にします。今回は、1203にします。 いくつかのchainIdは既に使われているので避けましょう。 { " config ": { " chainId ": 1203 , " homesteadBlock ": 0 , " eip150Block ": 0 , " eip155Block ": 0 , " eip158Block ": 0 , " byzantiumBlock ": 0 , " constantinopleBlock ": 0 , " petersburgBlock ": 0 , " istanbulBlock ": 0 , " berlinBlock ": 0 , " londonBlock ": 0 } , " alloc ": {} , " coinbase ": " 0x0000000000000000000000000000000000000000 ", " difficulty ": " 0x20000 ", " extraData ": "", " gasLimit ": " 0x2fefd8 ", " nonce ": " 0x0000000000000042 ", " mixhash ": " 0x0000000000000000000000000000000000000000000000000000000000000000 ", " parentHash ": " 0x0000000000000000000000000000000000000000000000000000000000000000 ", " timestamp ": " 0x00 " } 作成した genesis . json は、データ ディレクト リに保存します。 次のコマンドで、 Genesis ブロックを作成します。 $ geth init genesis.json INFO [11-29|16:32:24.485] Maximum peer count ETH=50 LES=0 total=50 INFO [11-29|16:32:24.499] Set global gas cap cap=25000000 INFO [11-29|16:32:24.499] Allocated cache and file handles database=/Users/higayasuo/Library/Ethereum/geth/chaindata cache=16.00MiB handles=16 INFO [11-29|16:32:24.576] Persisted trie from memory database nodes=0 size=0.00B time="6.87µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B Fatal: Failed to write genesis block: database contains incompatible genesis (have d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3, new 5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0) ログを見ると、 Failed to write genesis block: と処理に失敗しています。--datadirオプションでデータのための ディレクト リを指定していないので、ライブラリがインストールされている方の ディレクト リに書き込んで失敗しているのでしょう。 今度は、--datadirを指定して、geth initを呼び出します。--datadirで指定した ディレクト リにgethとkeystoreの ディレクト リがあれば、初期化はうまくいっています。 $ geth --datadir . init genesis.json 実は、gethを開発モード(--dev)で起動するといろんなものをあらかじめ準備してくれるので、すぐに送金などを試すことができます。しかし、Ethereumをきちんと理解するには、最初は自分で全部やってみるのが良いと思います。 Gethの起動 先ほど、指定したchainIdをnetworkidに指定して、Gethを起動します。 Gethはデフォルトで自動的に同じネットワークIDのノードを探し接続を試みます。今回、そのような動作は必要ありません。--nodiscoverを指定し無効にします。 $ geth --networkid 1203 --nodiscover ログを見てください。 IPC endpoint opened のログが下のほうにあるはずです。 INFO [11-29|17:03:04.231] IPC endpoint opened url=/Users/higayasuo/Library/Ethereum/geth.ipc geth.ipcが先ほど作成したデータ ディレクト リ(private_net)ではなく、$HOME/Library/Ethereumに作成されています。これは、--datadirオプションを指定し忘れたためです。CTRL-Cでプロセスをいったん止め、次のコマンドで再度起動しましょう。 $ geth --networkid 1203 --nodiscover --datadir . IPC endpoint opened のログを探して、データ ディレクト リ(private_net)にgeth.ipcが作成されたことを確認してください。geth.ipcは、今起動したプロセスに、別のGethがコンソールでアクセスするときに使います。 Gethのconsoleサブコマンドでコンソールとして起動することもできますが、お勧めしません。なぜかというと、Gethのいろんなログがコンソールに表示され使いにくいためです。また、コンソールを終了させるとGeth自身も終了してしまいます。Gethはconsoleなしで起動し、Gethを対話的に操作したい時に別のターミナル(プロセス)から geth attach を使って既に起動したGethのコンソールを立ち上げるのがおすすめです。 Geth attachサブコマンド 別のターミナルを立ち上げ、データ ディレクト リ(private_net)に移動しましょう。Gethのattachサブコマンドで、既に立ち上げたGethプロセスのコンソールを立ち上げることができます。 $ geth attach geth.ipc Welcome to the Geth JavaScript console! instance: Geth/v1.9.25-stable/darwin-amd64/go1.15.6 at block: 0 (Thu Jan 01 1970 09:00:00 GMT+0900 (JST)) datadir: /Users/higayasuo/dev/ethereum/private_net5 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d > Gethは プログラミング言語 Goで作られた CUI ですが、コンソールは JavaScript で出来ているようですね。 geth attach に--datadirをつけ忘れているのではと思う方もいらっしゃるかもしれません。これまでつけ忘れていつも失敗していましたからね。でも、たぶん大丈夫。 geth attach は既に起動したGethプロセスのコンソールなので、そちらに--datadirが付いていれば特に問題はないはず。 etherの採掘 何をやるにしてもまずは元手(ether)が必要です。採掘をしてみましょう。まずは、採掘の状態を確認します。 > eth.mining false まだ、採掘は始まっていませんね。minerモジュールを使って採掘を始めましょう。採掘は非同期に行われます。 > miner.start() Error: etherbase missing: etherbase must be explicitly specified at web3.js:6347:37(47) at web3.js:5081:62(37) at <eval>:1:12(3) etherbaseとは採掘の報酬を受け取るアカウントのことです。現在のアカウントの状態を調べてみましょう。 > eth.accounts [] まだ空っぽです。アカウントを作成しましょう。personalモジュールを使います。 > personal.newAccount() Passphrase: Repeat passphrase: "0x29faad1bb68151278c47df617766bf045c9b2b00" 最後に表示された16進数がアカウントのアドレスです。 eth.accounts をチェックしてみましょう。 > eth.accounts ["0x29faad1bb68151278c47df617766bf045c9b2b00"] もう一つ確認しておきたいのが、 eth.coinbase です。coinbaseはetherbaseと同じものです。 > eth.coinbase "0x29faad1bb68151278c47df617766bf045c9b2b00" coinbaseは eth.accounts[0] が自動で設定されます。 miner.setEtherbase() で eth.accounts[0] 以外のアカウントを設定することもできます。試してみましょう。 > personal.newAccount() Passphrase: Repeat passphrase: "0x0577d82aa10fea504320a087f822d8f68899f980" > eth.accounts ["0x29faad1bb68151278c47df617766bf045c9b2b00", "0x0577d82aa10fea504320a087f822d8f68899f980"] > miner.setEtherbase(eth.accounts[1]) true > eth.coinbase "0x0577d82aa10fea504320a087f822d8f68899f980" 二番目のアカウントが eth.coinbase になっていますね。 etherbaseを変更することが確認できたので、元に戻しておきましょう。 > miner.setEtherbase(eth.accounts[0]) true > eth.coinbase "0x29faad1bb68151278c47df617766bf045c9b2b00" etherbaseが設定できたので、採掘を開始しましょうと言いたいところですが、その前にいくつか確認しておきたいことがあります。一つ目は、 eth.blockNumber 。 > eth.blockNumber 0 0といってもブロックがないわけではなく、 Genesis ブロックのblockNumberが0だということです。blockNumberは採掘が進めば増えていきます。ブロックの中身も eth.getBlock() で確認しておきましょう。logsBloomだけ長いの省略しています。 > eth.getBlock(0) { difficulty: 131072, extraData: "0x", gasLimit: 3141592, gasUsed: 0, hash: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0", logsBloom: "0x000000000000000000000000000000省略", miner: "0x0000000000000000000000000000000000000000", mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0000000000000042", number: 0, parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 507, stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", timestamp: 0, totalDifficulty: 131072, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] } これをみても「これが Genesis ブロックなのか」という実感は湧かないと思いますが、こんなものだと思ってください。アカウントの残高も確認しておきましょう。 > eth.getBalance(eth.accounts[0]) 0 > eth.getBalance(eth.accounts[1]) 0 お待たせしました。採掘を開始しましょう。 > miner.start() null > eth.mining true Gethのメインプロセスのログも見てみましょう。モリモリ採掘しているのが分かります。 eth.blockNumber も確認しましょう。 > eth.blockNumber 127 数が増えているので、きちんと採掘されていることが確認できます。アカウントの残高も確認しておきましょう。 > eth.getBalance(eth.accounts[0]) 2.27e+21 > eth.getBalance(eth.accounts[1]) 0 残高の単位はweiといって、1 ether = 1e+18 weiです。今、1 etherは50万くらいなので、 eth.accounts[0] の残高は1億円超えてますね。 etherの送金 etherの送金は、 eth.sendTransaction() を使います。 from に送金元アカウント、 to に送金先アカウント、 value に送金額をweiで指定します。weiよりetherの方が分かりやすいという場合には、 web3.toWei() を使うといいです。次の例では、2 etherをweiに変換しています。 > web3.toWei(2, 'ether') "2000000000000000000" それでは、 eth.accounts[0] から eth.accounts[1] に 2 ether 送金してみましょう。 > tx = {from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(2, 'ether')} { from: "0x29faad1bb68151278c47df617766bf045c9b2b00", to: "0x0577d82aa10fea504320a087f822d8f68899f980", value: "2000000000000000000" } > eth.sendTransaction(tx) Error: authentication needed: password or unlock at web3.js:6347:37(47) at web3.js:5081:62(37) at <eval>:1:20(4) おや、 Error: authentication needed: password or unlock のエラーが出てしまいました。送金元は、アンロックしておく必要があります。アンロックはデフォルトで5分間持続します。アンロック後、5分以上経過した場合、送金したいなら再度アンロックする必要があります。 personal.unlockAccount() の三番目の引数で、アンロックが持続する時間を秒で指定することもできます。二番目の引数はパスワードなので、パスワードが、見えてしまう問題が生じいまいちですね。 > personal.unlockAccount(eth.accounts[0]) Unlock account 0x29faad1bb68151278c47df617766bf045c9b2b00 Passphrase: true > eth.sendTransaction(tx) "0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0" 最後に表示されたのは、 トランザクション IDです。 eth.getTransaction() で中身を確認してみましょう。 > eth.getTransaction("0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0") { blockHash: "0xe510ee2dc063657b6506b85c78ec361c77d6acc4755b664f2b94faee34b934dd", blockNumber: 4702, from: "0x29faad1bb68151278c47df617766bf045c9b2b00", gas: 21000, gasPrice: 1000000000, hash: "0x5d1a835279c0050037fffce65597afed3dd26ca0f7155662e2239746b08621f0", input: "0x", nonce: 0, r: "0x407f7a5709fc864a8d485cde854c880329475539b773d0e429de0e324f0241c9", s: "0x4998ca73871d2a4dd05374abeb7352f9e8a3c48c1bc288cfe9dbca0ba6206300", to: "0x0577d82aa10fea504320a087f822d8f68899f980", transactionIndex: 0, v: "0x989", value: 2000000000000000000 } from, to, value がきちんと設定されていますね。blockHash, blockNumberが設定されているので、この トランザクション は採掘者によって採掘済みであることがわかります。いつまでたっても、blockHash, blockNumberが設定されない場合は、採掘処理が動いていないかもしれません。 eth.mining で確認して false の場合は、 miner.start() させましょう。 送金先の残高が増えていることを確認しましょう。 web3.fromWei() でweiからetherに単位を変換できます。 > web3.fromWei(eth.getBalance(eth.accounts[1]), 'ether') 2 トランザクション を処理するとき、採掘者に支払われる手数料をgas代といいます。 トランザクション 情報にあったgasの値は、gas代ではありません。 トランザクション 処理時におけるgas使用量の最大値を示しています。 それでは、gas代を知るにはどうしたら良いのでしょうか。送金の時に、送金元の残高から、送金額とgas代が引かれます。そこから計算する必要があります。 eth.accounts[0] は、採掘で常に残高が増え続けているので、gas代を計算するのに向いていません。 eth.accounts[1] から eth.accounts[0] に 1 ether 送金してみましょう。 eth.accounts[1] の現在の残高は、 2 ether ですから 1 ether より少なくなった分がgas代になります。 eth.accounts[1] が送金元になるので、アカウントをアンロックしておく必要があります。失敗から学ぶと言っても同じ失敗を繰り返してはダメですよ。 > personal.unlockAccount(eth.accounts[1]) Unlock account 0x0577d82aa10fea504320a087f822d8f68899f980 Passphrase: true > tx2 = {from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(1, 'ether')} { from: "0x0577d82aa10fea504320a087f822d8f68899f980", to: "0x29faad1bb68151278c47df617766bf045c9b2b00", value: "1000000000000000000" } > txId2 = eth.sendTransaction(tx2) "0xf9c427c253bfd6980c236a691393e1cc501b1d3d71deaf5ddcf17f2d3fca206f" > txInfo2 = eth.getTransaction(txId2) { blockHash: "0xbf4c0d1ccc2325ad2ada70148b818724119ba582d4f0f765b3f56edc36cabc4f", blockNumber: 6378, from: "0x0577d82aa10fea504320a087f822d8f68899f980", gas: 21000, gasPrice: 1000000000, hash: "0xf9c427c253bfd6980c236a691393e1cc501b1d3d71deaf5ddcf17f2d3fca206f", input: "0x", nonce: 0, r: "0x2703788aaad9dd2ae6d08802ae555cdbdbdd1a6dfc432aa94fd1ccc5f4efe8a0", s: "0x413213c0447df661f8ef204e21654ff41894d219a3181f17cad30895aea4034e", to: "0x29faad1bb68151278c47df617766bf045c9b2b00", transactionIndex: 0, v: "0x98a", value: 1000000000000000000 } > gasFee2 = web3.toWei(1, 'ether') - eth.getBalance(eth.accounts[1]) 21000000000000 > gas2 = gasFee2 / txInfo2.gasPrice 21000 gasFee2が今回の トランザクション のgas代です。gasPriceは1gasあたりの手数料です。gas代をgasPriceで割ると今回、何gas使用したかが分かります。 今回はここまで。Gethを止めておきましょう。コンソールはCTL-D。メインプロセスはCTL-Cで止めます。 次回は、スマート コントラ クトを扱います。 僕の書いたNFT関連の記事 NFT入門 スマートコントラクト入門 執筆: @higa 、レビュー: @sato.taichi ( Shodo で執筆されました )
はいどーもー! X イノベーション 本部の宮澤響です! 本記事は 電通国際情報サービス Advent Calendar 2021 2日目の記事です! 記念すべき1日目である昨日の記事は、佐藤太一さんの「 テックブログ始めました。 」でした!我々の記事をホストするためのサービスとして はてなブログ を採用した理由や、記事を執筆するにあたって利用しているツールについて分かりやすくまとめられていますので、ぜひご一読ください! 本記事では、「 GitHub Actionsと AWS App Runnerを利用してBlue/Greenデプロイメントを実現してみた」というタイトルのとおり、 GitHub Actionsと AWS App Runner(以下、App Runner)、それに加えて、 Amazon CloudFront (以下、CloudFront)と Amazon Route 53(以下、Route 53)を利用してBlue/Greenデプロイメントを実現する方法を、サンプルコードとともにご紹介します! Blue/Greenデプロイメントって何? App Runnerって何? 事前に準備すること App Runnerサービスを作成する CloudFrontディストリビューションを作成する Route 53のレコードを作成する GitHubのRepository secretsを設定する GitHub ActionsのワークフローのYAMLファイルを作成する 実際にBlue/Greenデプロイメントしてみる GitHub ActionsのワークフローのYAMLファイルの改善方法 GitHub Actionsのログ出力の制御 OpenID Connectを利用したAWSリソースへのアクセス CloudFrontディストリビューションのキャッシュパージ まとめ Blue/Greenデプロイメントって何? Blue/Greenデプロイメントとは、本番環境と検証環境を交互に入れ替えることにより、Webアプリケーションなどに変更を加えるデプロイ手法の一つです。ざっくりとした流れとしては、「本番環境であるBlue」と「本番環境とほぼ同一の検証環境Green」の2つを用意した上で、以下の1.〜4.を繰り返すことになります。 Greenに変更を加えていく 問題がなければGreenを本番環境に、Blueを検証環境にチェンジする(デプロイ) 今度はBlueに変更を加えていく 問題がなければ再びBlueを本番環境に、Greenを検証環境にチェンジする(デプロイ) メリットとしては、デプロイ時や ロールバック 時のシステムのダウンタイムを最小限にできることや、限りなく本番環境に近い検証環境でテストできることなどが挙げられます。 一方、デメリットとしては、 アーキテクチャ が複雑になることや、インフラ構築用のコードと実際のインフラ環境に矛盾が生じる(インフラ構築用のコード上でBlueを本番環境としていた場合、Greenが本番環境になっている間はインフラ構築用のコードと実際のインフラ環境が一致しない)ことなどが挙げられます。 App Runnerって何? App Runnerとは、コンテナベースの AWS リソースの一つです。噛み砕いて言えば、 Amazon ECS、 AWS Fargate、ELBといった種々のリソースを全部まとめて裏でイイ感じにやってくれるものです。非常に手軽で簡単にWebアプリケーションをデプロイできる反面、制約も多いです。詳しくは 公式ドキュメント を参照ください。 事前に準備すること 各種 AWS リソースや、Blue/Greenデプロイメントを実現するための GitHub Actionsのワークフローを作成します。最終的な アーキテクチャ は下図になります。 App Runnerサービスを作成する まずは、 公式ドキュメント の手順に従い、サンプルアプリケーションがデプロイされているApp Runnerサービスを2つ作成します。 Prerequisites の手順に従い、サンプル リポジトリ を作成します。 リポジトリ 名は任意でOKです。 ステップ 1: App Runner サービスを作成する の手順に従い、App Runnerサービスを作成します。 GitHub connectionsの接続名や 環境変数 NAME の値は任意でOKです。サービス名はそれぞれ sample-service-blue 、 sample-service-green とします。また、デプロイ設定の部分で、 sample-service-blue のデプロイトリガーを 手動 に、 sample-service-green のデプロイトリガーを 自動 に、それぞれ設定してください。 何故片方を手動デプロイ、もう片方を自動デプロイにするかというと、自動デプロイの場合、指定した リポジトリ 、ブランチの ソースコード に変更を加える(=pushする)度に、App Runnerサービスにも変更が反映される(=最新の ソースコード を基にアプリケーションがデプロイし直される)ためです。これにより、本番環境(手動デプロイ)に影響を与えることなく、検証環境(自動デプロイ)に変更を加えることが可能となります。 ソースコード をpushするだけで最新のアプリケーションが自動でデプロイされるのは非常に手軽で便利ですね! CloudFront ディストリビューション を作成する 次に、 公式ドキュメント の手順に従い、CloudFront ディストリビューション を2つ作成します。基本的にはデフォルト設定のままで問題ありませんが、以下の項目はデフォルトから変更をお願いします。 2つの ディストリビューション 共通の設定 項目 値 プロトコル HTTPSのみ 料金クラス 北米、欧州、アジア、中東、アフリカを使用 (Blue/Greenデプロイメントには直接関係ありませんが、コストを抑えるためです) カスタム SSL証明書 任意の証明書 (本記事では example.com とします) それぞれの ディストリビューション で異なる設定 項目 1つ目の ディストリビューション に設定する値 2つ目の ディストリビューション に設定する値 オリジン ドメイン sample-service-blue のデフォルト ドメイン ( https:// の部分は不要です) sample-service-green のデフォルト ドメイン ( https:// の部分は不要です) 代替 ドメイン 名 カスタム SSL証明書 に対応する任意の ドメイン 名 (本記事では sample.example.com とします) なし 説明 sample-distribution-blue sample-distribution-green 何故片方だけに代替 ドメイン 名を入力するかというと、CloudFrontのルールとして、複数の ディストリビューション に同一の代替 ドメイン を同時に設定できないようになっているためです。つまり、代替 ドメイン は、常にルーティング先が本番環境になっている方の ディストリビューション にのみ設定されるように、適宜付け替える必要があります。 Route 53のレコードを作成する 続いて、 公式ドキュメント の手順に従い、Route 53のレコードを2つ作成します。 1つ目(Aレコード) 項目 値 レコード名 sample-distribution-blue の代替 ドメイン 名と対応する名称 (本記事では sample とします) レコードタイプ A の エイリアス トラフィック のルーティング先 CloudFrontディストリビューションへのエイリアス > sample-distribution-blue の ドメイン 名 ルーティングポリシー シンプルルーティング 2つ目(TXTレコード) 項目 値 レコード名 Aレコードのレコード名の先頭に _ を付したもの (本記事では _sample とします) レコードタイプ TXT トラフィック のルーティング先 sample-distribution-green の ドメイン 名の末尾に . を付したもの ルーティングポリシー シンプルルーティング 何故2つ目のTXTレコードが必要になるかというと、 CloudFrontディストリビューションを作成する の節で説明した代替 ドメイン の付け替えに必要になるためです。このレコードに、代替 ドメイン を設定しない方の ディストリビューション の ドメイン を設定しておく必要があります。レコード名の先頭に _ を付けたり値の末尾に . を付けたりするのは仕様です。詳しくは 公式ドキュメント を参照ください。 GitHub のRepository secretsを設定する ここからは GitHub 側の準備になります。 GitHub Actionsの利用に際して、 AWS の認証情報や間接的に取得することが難しい値を、あらかじめRepository secretsに設定しておきます。 公式ドキュメント の手順に従い、以下の4つを設定してください。 Name Value AWS_ACCESS_KEY_ID 自身の AWS アクセスキーID AWS_SECRET_ACCESS_KEY 自身の AWS シークレットアクセスキー AWS_ROUTE53_HOSTED_ZONE_ID Route 53のホストゾーンID AWS_ROUTE53_RECORD_NAME sample.example.com (Route 53のAレコードのレコード名) GitHub Actionsのワークフローの YAML ファイルを作成する 最後に、実際にBlue/Greenデプロイメントを実現する部分である、 GitHub Actionsのワークフローの YAML ファイルを作成します。今回の構成でBlue/Greenデプロイメントの実現に必要な要素は、以下の4つです。 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する CloudFront ディストリビューション の代替 ドメイン 名を付け替える Route 53のAレコードとTXTレコードのルーティング先を入れ替える まずはこれらを、 AWS CLI のコマンドを用いて GitHub Actionsのワークフローに落とし込みます。なお、以下の例では、 release/〇〇 のようなタグのpushをBlue/Greenデプロイメントのトリガーとしています。 name : Blue/Green Deploy on : push : tags : - release/* jobs : deploy : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 # 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する - name : Change Source Configuration of Current Service run : aws apprunner update-service --service-arn `本番環境側のApp RunnerサービスのARN` --source-configuration `設定ファイルのパス` # 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する - name : Change Source Configuration of Next Service run : aws apprunner update-service --service-arn `検証環境側のApp RunnerサービスのARN` --source-configuration `設定ファイルのパス` # CloudFrontディストリビューションの代替ドメイン名を付け替える - name : Replace Alias run : aws cloudfront associate-alias --target-distribution-id `検証環境側のCloudFrontディストリビューションのID` --alias ${{ secrets.AWS_ROUTE53_RECORD_NAME }} # Route 53のAレコードとTXTレコードのルーティング先を入れ替える - name : Change Record Targets run : aws route53 change-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --change-batch `設定ファイルのパス` このワークフローを実行することにより、現在の検証環境が本番環境となり、Aレコードの ドメイン からアクセス可能になります。一方、現在の本番環境は検証環境となり、 ソースコード をpushするだけで最新のアプリケーションが自動でデプロイされます。 しかしながら、このままでは現在の本番環境がどちらかをその都度手動で調べたり、各リソースのARNやIDなどをコピペしたり、設定ファイルを自作したりする必要があります。当然、そんなことをしていては非常に面倒ですし、ヒューマンエラーも発生しやすくなってしまいます。 そのため、それらの処理も併せて自動化したものが以下になります。 name : Blue/Green Deploy on : push : tags : - release/* jobs : deploy : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 # CloudFrontディストリビューションの一覧を取得し、その中から説明が`sample-distribution`で始まっているものの情報をファイルに書き込む # 代替ドメインが1個のものを現在の本番環境、0個のものを検証環境と判定する - name : Get Distribution List run : | aws cloudfront list-distributions | jq '.DistributionList.Items[] | select(.Comment | startswith("sample-distribution"))' > distribution-list.json cat distribution-list.json | jq 'select(.Aliases.Quantity==1)' > current-distribution.json cat distribution-list.json | jq 'select(.Aliases.Quantity==0)' > next-distribution.json # 2つのCloudFrontディストリビューションのドメイン名を取得し、変数に代入する - name : Get Distribution Domains id : distribution-domains run : | echo "::set-output name=CURRENT_DISTRIBUTION_DOMAIN::$(cat current-distribution.json | jq -r '.DomainName')" echo "::set-output name=NEXT_DISTRIBUTION_DOMAIN::$(cat next-distribution.json | jq -r '.DomainName')" # 検証環境側のCloudFrontディストリビューションのIDを取得し、変数に代入する - name : Get Distribution ID id : distribution-id run : echo "::set-output name=NEXT_DISTRIBUTION_ID::$(cat next-distribution.json | jq -r '.Id')" # App Runnerサービスの一覧を取得し、ファイルに書き込む - name : Get Service List run : aws apprunner list-services | jq '.ServiceSummaryList[]' > service-list.json # 2つのApp Runnerサービスのドメインを取得し、変数に代入する - name : Get Service Domains id : service-domains run : | echo "::set-output name=CURRENT_SERVICE_DOMAIN::$(cat current-distribution.json | jq -r '.Origins.Items[0].DomainName')" echo "::set-output name=NEXT_SERVICE_DOMAIN::$(cat next-distribution.json | jq -r '.Origins.Items[0].DomainName')" # 2つのApp RunnerサービスのARNを取得し、変数に代入する - name : Get Service ARNs id : service-arns run : | echo "::set-output name=CURRENT_SERVICE_ARN::$(cat service-list.json | jq -r --arg domain ${{ steps.service-domains.outputs.CURRENT_SERVICE_DOMAIN }} 'select(.ServiceUrl==$domain) | .ServiceArn')" echo "::set-output name=NEXT_SERVICE_ARN::$(cat service-list.json | jq -r --arg domain ${{ steps.service-domains.outputs.NEXT_SERVICE_DOMAIN }} 'select(.ServiceUrl==$domain) | .ServiceArn')" # 2つのApp Runnerサービスの設定情報を取得し、デプロイトリガー部分を変更した上でファイルに書き込む - name : Get Service Config Files run : | aws apprunner describe-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} | jq '.Service.SourceConfiguration | .AutoDeploymentsEnabled=true' > current-service-config.json aws apprunner describe-service --service-arn ${{ steps.service-arns.outputs.NEXT_SERVICE_ARN }} | jq '.Service.SourceConfiguration | .AutoDeploymentsEnabled=false' > next-service-config.json # 指定したホストゾーンIDのRoute 53のレコードの一覧を取得し、指定したレコード名のものと、指定したレコード名の先頭に`_`を付したものの情報をファイルに書き込む # 2つのレコードの設定部分を変更した上でファイルに書き込む - name : Get Record Config File run : | aws route53 list-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --output json | jq --arg name ${{ secrets.AWS_ROUTE53_RECORD_NAME }} '.ResourceRecordSets[] | select(.Name | endswith($name+"."))' > record-list.json cat record-list.json | jq --arg domain ${{ steps.distribution-domains.outputs.CURRENT_DISTRIBUTION_DOMAIN }} 'select(.Type=="TXT") | .ResourceRecords[0].Value="\""+$domain+".\"" | {"Changes":[{"Action":"UPSERT"}+{ResourceRecordSet:.}]}' > txt-record.json cat record-list.json | jq --arg domain ${{ steps.distribution-domains.outputs.NEXT_DISTRIBUTION_DOMAIN }} 'select(.Type=="A") | .AliasTarget.DNSName=$domain+"." | {"Changes":[{"Action":"UPSERT"}+{ResourceRecordSet:.}]}' > a-record.json cat txt-record.json a-record.json | jq -s '.[0].Changes+.[1].Changes | {"Changes":.}' > record-config.json # 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する - name : Change Source Configuration of Current Service run : aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} --source-configuration file://current-service-config.json # 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する - name : Change Source Configuration of Next Service run : aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.NEXT_SERVICE_ARN }} --source-configuration file://next-service-config.json # CloudFrontディストリビューションの代替ドメイン名を付け替える - name : Replace Alias run : aws cloudfront associate-alias --target-distribution-id ${{ steps.distribution-id.outputs.NEXT_DISTRIBUTION_ID }} --alias ${{ secrets.AWS_ROUTE53_RECORD_NAME }} # Route 53のAレコードとTXTレコードのルーティング先を入れ替える - name : Change Record Targets run : aws route53 change-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --change-batch file://record-config.json # 作成したファイルを削除する - name : Delete Temporary Files if : ${{ always() }} run : rm distribution-list.json current-distribution.json next-distribution.json service-list.json current-service-config.json next-service-config.json record-list.json txt-record.json a-record.json record-config.json 実際にBlue/Greenデプロイメントしてみる それでは、実際にBlue/Greenデプロイメントのワークフローを動作させ、環境の切り替わりを確認してみます。 現在の本番環境の状態を確認するために、 sample.example.com にアクセスします。 サンプルアプリケーションの文字列がそのまま表示されます。 現在の検証環境の状態を確認するために、 sample-distribution-green の ドメイン にアクセスします。 本番環境と同一の画面が表示されます。 server.py の8行目の Hello を こんにちは に変更して、 GitHub リポジトリ にpushします。 MESSAGE = "こんにちは, " + name + "!" sample-service-green へのデプロイが完了するのを待ってから、再度 sample-distribution-green の ドメイン にアクセスしてみます。 こんにちは に更新されているため、検証環境には先ほどpushした内容が反映されていることが分かります。 再度 sample.example.com にアクセスしてみます。 Hello のままであるため、先ほどのpushが本番環境には影響を与えていないことが分かります。 release/v1.0.0 というタグをpushします。 GitHub Actionsのワークフローが正常に完了していれば成功です。 git tag release/v1.0.0 git push origin release/v1.0.0 再度 sample.example.com にアクセスしてみます。 こんにちは に更新されています。Blue/Greenデプロイメントにより、先ほどまでの検証環境が数十秒のうちに本番環境に切り替わったことが確認できました! なお、確認は省略しますが、今回確認したBlueからGreenへの切り替えだけでなく、GreenからBlueへの切り替えも正常に動作します。 GitHub Actionsのワークフローの YAML ファイルの改善方法 GitHub Actionsのログ出力の制御 本記事にサンプルとして掲載した YAML ファイルによるワークフローを実行すると、Repository secrets以外の変数の値や AWS CLI のコマンドの実行結果が GitHub Actionsのログに出力されてしまいます。そのため、Publicな リポジトリ で実行する場合には、 ログ中での値のマスク や nul へのリダイレクトなどを利用して、ログの出力を工夫する必要があります。 # 値のマスクの例 CURRENT_DISTRIBUTION_DOMAIN=$(cat current-distribution.json | jq -r '.DomainName') echo "::add-mask::$CURRENT_DISTRIBUTION_DOMAIN" echo "::set-output name=CURRENT_DISTRIBUTION_DOMAIN::$CURRENT_DISTRIBUTION_DOMAIN" # nulへのリダイレクトの例 aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} --source-configuration file://current-service-config.json > nul OpenID Connectを利用した AWS リソースへのアクセス GitHub Actionsから AWS リソースにアクセスする方法に関しては、 OpenID Connectを利用する方法が先日発表されました。こちらの方法を利用すると、 AWS アクセスキーIDや AWS シークレットアクセスキーを利用せずに AWS リソースにアクセスできるため、より安全性を高められます。詳しくは、 公式ドキュメント や、テックブログ記事である OpenID Connectを利用してGitHub ActionsからAWSリソースにアクセスする を参照ください。 CloudFront ディストリビューション のキャッシュパージ Blue/Greenデプロイメントのワークフローの YAML ファイルとは別に、 main ブランチへのpushをトリガーとして検証環境側のCloudFront ディストリビューション のキャッシュをパージするワークフローの YAML ファイルを作成することにより、アプリケーションをデプロイし直す際にキャッシュによって古い情報が配信されることを防げます。 # キャッシュパージの例 NEXT_DISTRIBUTION_ID=$(aws cloudfront list-distributions | jq -r '.DistributionList.Items[] | select(.Comment | startswith("sample-distribution")) | select(.Aliases.Quantity==0) | .Id') aws cloudfront create-invalidation --distribution-id $NEXT_DISTRIBUTION_ID --paths "/*" まとめ 本記事では、 GitHub Actions、App Runner、CloudFront、Route 53を利用してBlue/Greenデプロイメントを実現する方法をご紹介しました。 GitHub リポジトリ に ソースコード をpushするだけで検証環境にアプリケーションがデプロイされ、タグをpushするだけでBlue/Greenデプロイメントが完了するというのは、開発活動を進めていく上で非常に便利で快適です。機会があれば皆さんもぜひお試しください! 電通国際情報サービス Advent Calendar 2021 3日目となる明日の記事は 比嘉康雄 さんの「 Geth(ゲス)はじめました 」です!お楽しみに! 最後までお読みいただき、本当にありがとうございました! 執筆: @miyazawa.hibiki 、レビュー: @sato.taichi ( Shodo で執筆されました )
はいどーもー! X イノベーション 本部の宮澤響です! 本記事は 電通国際情報サービス Advent Calendar 2021 2日目の記事です! 記念すべき1日目である昨日の記事は、佐藤太一さんの「 テックブログ始めました。 」でした!我々の記事をホストするためのサービスとして はてなブログ を採用した理由や、記事を執筆するにあたって利用しているツールについて分かりやすくまとめられていますので、ぜひご一読ください! 本記事では、「 GitHub Actionsと AWS App Runnerを利用してBlue/Greenデプロイメントを実現してみた」というタイトルのとおり、 GitHub Actionsと AWS App Runner(以下、App Runner)、それに加えて、 Amazon CloudFront (以下、CloudFront)と Amazon Route 53(以下、Route 53)を利用してBlue/Greenデプロイメントを実現する方法を、サンプルコードとともにご紹介します! Blue/Greenデプロイメントって何? App Runnerって何? 事前に準備すること App Runnerサービスを作成する CloudFrontディストリビューションを作成する Route 53のレコードを作成する GitHubのRepository secretsを設定する GitHub ActionsのワークフローのYAMLファイルを作成する 実際にBlue/Greenデプロイメントしてみる GitHub ActionsのワークフローのYAMLファイルの改善方法 GitHub Actionsのログ出力の制御 OpenID Connectを利用したAWSリソースへのアクセス CloudFrontディストリビューションのキャッシュパージ おわりに Blue/Greenデプロイメントって何? Blue/Greenデプロイメントとは、本番環境と検証環境を交互に入れ替えることにより、Webアプリケーションなどに変更を加えるデプロイ手法の一つです。ざっくりとした流れとしては、「本番環境であるBlue」と「本番環境とほぼ同一の検証環境Green」の2つを用意した上で、以下の1.〜4.を繰り返すことになります。 Greenに変更を加えていく 問題がなければGreenを本番環境に、Blueを検証環境にチェンジする(デプロイ) 今度はBlueに変更を加えていく 問題がなければ再びBlueを本番環境に、Greenを検証環境にチェンジする(デプロイ) メリットとしては、デプロイ時や ロールバック 時のシステムのダウンタイムを最小限にできることや、限りなく本番環境に近い検証環境でテストできることなどが挙げられます。 一方、デメリットとしては、 アーキテクチャ が複雑になることや、インフラ構築用のコードと実際のインフラ環境に矛盾が生じる(インフラ構築用のコード上でBlueを本番環境としていた場合、Greenが本番環境になっている間はインフラ構築用のコードと実際のインフラ環境が一致しない)ことなどが挙げられます。 App Runnerって何? App Runnerとは、コンテナベースの AWS リソースの一つです。噛み砕いて言えば、 Amazon ECS、 AWS Fargate、ELBといった種々のリソースを全部まとめて裏でイイ感じにやってくれるものです。非常に手軽で簡単にWebアプリケーションをデプロイできる反面、制約も多いです。詳しくは 公式ドキュメント を参照ください。 事前に準備すること 各種 AWS リソースや、Blue/Greenデプロイメントを実現するための GitHub Actionsのワークフローを作成します。最終的な アーキテクチャ は下図になります。 App Runnerサービスを作成する まずは、 公式ドキュメント の手順に従い、サンプルアプリケーションがデプロイされているApp Runnerサービスを2つ作成します。 Prerequisites の手順に従い、サンプル リポジトリ を作成します。 リポジトリ 名は任意でOKです。 ステップ 1: App Runner サービスを作成する の手順に従い、App Runnerサービスを作成します。 GitHub connectionsの接続名や 環境変数 NAME の値は任意でOKです。サービス名はそれぞれ sample-service-blue 、 sample-service-green とします。また、デプロイ設定の部分で、 sample-service-blue のデプロイトリガーを 手動 に、 sample-service-green のデプロイトリガーを 自動 に、それぞれ設定してください。 何故片方を手動デプロイ、もう片方を自動デプロイにするかというと、自動デプロイの場合、指定した リポジトリ 、ブランチの ソースコード に変更を加える(=pushする)度に、App Runnerサービスにも変更が反映される(=最新の ソースコード を基にアプリケーションがデプロイし直される)ためです。これにより、本番環境(手動デプロイ)に影響を与えることなく、検証環境(自動デプロイ)に変更を加えることが可能となります。 ソースコード をpushするだけで最新のアプリケーションが自動でデプロイされるのは非常に手軽で便利ですね! CloudFront ディストリビューション を作成する 次に、 公式ドキュメント の手順に従い、CloudFront ディストリビューション を2つ作成します。基本的にはデフォルト設定のままで問題ありませんが、以下の項目はデフォルトから変更をお願いします。 2つの ディストリビューション 共通の設定 項目 値 プロトコル HTTPSのみ 料金クラス 北米、欧州、アジア、中東、アフリカを使用 (Blue/Greenデプロイメントには直接関係ありませんが、コストを抑えるためです) カスタム SSL証明書 任意の証明書 (本記事では example.com とします) それぞれの ディストリビューション で異なる設定 項目 1つ目の ディストリビューション に設定する値 2つ目の ディストリビューション に設定する値 オリジン ドメイン sample-service-blue のデフォルト ドメイン ( https:// の部分は不要です) sample-service-green のデフォルト ドメイン ( https:// の部分は不要です) 代替 ドメイン 名 カスタム SSL証明書 に対応する任意の ドメイン 名 (本記事では sample.example.com とします) なし 説明 sample-distribution-blue sample-distribution-green 何故片方だけに代替 ドメイン 名を入力するかというと、CloudFrontのルールとして、複数の ディストリビューション に同一の代替 ドメイン を同時に設定できないようになっているためです。つまり、代替 ドメイン は、常にルーティング先が本番環境になっている方の ディストリビューション にのみ設定されるように、適宜付け替える必要があります。 Route 53のレコードを作成する 続いて、 公式ドキュメント の手順に従い、Route 53のレコードを2つ作成します。 1つ目(Aレコード) 項目 値 レコード名 sample-distribution-blue の代替 ドメイン 名と対応する名称 (本記事では sample とします) レコードタイプ A の エイリアス トラフィック のルーティング先 CloudFrontディストリビューションへのエイリアス > sample-distribution-blue の ドメイン 名 ルーティングポリシー シンプルルーティング 2つ目(TXTレコード) 項目 値 レコード名 Aレコードのレコード名の先頭に _ を付したもの (本記事では _sample とします) レコードタイプ TXT トラフィック のルーティング先 sample-distribution-green の ドメイン 名の末尾に . を付したもの ルーティングポリシー シンプルルーティング 何故2つ目のTXTレコードが必要になるかというと、 CloudFrontディストリビューションを作成する の節で説明した代替 ドメイン の付け替えに必要になるためです。このレコードに、代替 ドメイン を設定しない方の ディストリビューション の ドメイン を設定しておく必要があります。レコード名の先頭に _ を付けたり値の末尾に . を付けたりするのは仕様です。詳しくは 公式ドキュメント を参照ください。 GitHub のRepository secretsを設定する ここからは GitHub 側の準備になります。 GitHub Actionsの利用に際して、 AWS の認証情報や間接的に取得することが難しい値を、あらかじめRepository secretsに設定しておきます。 公式ドキュメント の手順に従い、以下の4つを設定してください。 Name Value AWS_ACCESS_KEY_ID 自身の AWS アクセスキーID AWS_SECRET_ACCESS_KEY 自身の AWS シークレットアクセスキー AWS_ROUTE53_HOSTED_ZONE_ID Route 53のホストゾーンID AWS_ROUTE53_RECORD_NAME sample.example.com (Route 53のAレコードのレコード名) GitHub Actionsのワークフローの YAML ファイルを作成する 最後に、実際にBlue/Greenデプロイメントを実現する部分である、 GitHub Actionsのワークフローの YAML ファイルを作成します。今回の構成でBlue/Greenデプロイメントの実現に必要な要素は、以下の4つです。 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する CloudFront ディストリビューション の代替 ドメイン 名を付け替える Route 53のAレコードとTXTレコードのルーティング先を入れ替える まずはこれらを、 AWS CLI のコマンドを用いて GitHub Actionsのワークフローに落とし込みます。なお、以下の例では、 release/〇〇 のようなタグのpushをBlue/Greenデプロイメントのトリガーとしています。 name : Blue/Green Deploy on : push : tags : - release/* jobs : deploy : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 # 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する - name : Change Source Configuration of Current Service run : aws apprunner update-service --service-arn `本番環境側のApp RunnerサービスのARN` --source-configuration `設定ファイルのパス` # 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する - name : Change Source Configuration of Next Service run : aws apprunner update-service --service-arn `検証環境側のApp RunnerサービスのARN` --source-configuration `設定ファイルのパス` # CloudFrontディストリビューションの代替ドメイン名を付け替える - name : Replace Alias run : aws cloudfront associate-alias --target-distribution-id `検証環境側のCloudFrontディストリビューションのID` --alias ${{ secrets.AWS_ROUTE53_RECORD_NAME }} # Route 53のAレコードとTXTレコードのルーティング先を入れ替える - name : Change Record Targets run : aws route53 change-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --change-batch `設定ファイルのパス` このワークフローを実行することにより、現在の検証環境が本番環境となり、Aレコードの ドメイン からアクセス可能になります。一方、現在の本番環境は検証環境となり、 ソースコード をpushするだけで最新のアプリケーションが自動でデプロイされます。 しかしながら、このままでは現在の本番環境がどちらかをその都度手動で調べたり、各リソースのARNやIDなどをコピペしたり、設定ファイルを自作したりする必要があります。当然、そんなことをしていては非常に面倒ですし、ヒューマンエラーも発生しやすくなってしまいます。 そのため、それらの処理も併せて自動化したものが以下になります。 name : Blue/Green Deploy on : push : tags : - release/* jobs : deploy : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ap-northeast-1 # CloudFrontディストリビューションの一覧を取得し、その中から説明が`sample-distribution`で始まっているものの情報をファイルに書き込む # 代替ドメインが1個のものを現在の本番環境、0個のものを検証環境と判定する - name : Get Distribution List run : | aws cloudfront list-distributions | jq '.DistributionList.Items[] | select(.Comment | startswith("sample-distribution"))' > distribution-list.json cat distribution-list.json | jq 'select(.Aliases.Quantity==1)' > current-distribution.json cat distribution-list.json | jq 'select(.Aliases.Quantity==0)' > next-distribution.json # 2つのCloudFrontディストリビューションのドメイン名を取得し、変数に代入する - name : Get Distribution Domains id : distribution-domains run : | echo "::set-output name=CURRENT_DISTRIBUTION_DOMAIN::$(cat current-distribution.json | jq -r '.DomainName')" echo "::set-output name=NEXT_DISTRIBUTION_DOMAIN::$(cat next-distribution.json | jq -r '.DomainName')" # 検証環境側のCloudFrontディストリビューションのIDを取得し、変数に代入する - name : Get Distribution ID id : distribution-id run : echo "::set-output name=NEXT_DISTRIBUTION_ID::$(cat next-distribution.json | jq -r '.Id')" # App Runnerサービスの一覧を取得し、ファイルに書き込む - name : Get Service List run : aws apprunner list-services | jq '.ServiceSummaryList[]' > service-list.json # 2つのApp Runnerサービスのドメインを取得し、変数に代入する - name : Get Service Domains id : service-domains run : | echo "::set-output name=CURRENT_SERVICE_DOMAIN::$(cat current-distribution.json | jq -r '.Origins.Items[0].DomainName')" echo "::set-output name=NEXT_SERVICE_DOMAIN::$(cat next-distribution.json | jq -r '.Origins.Items[0].DomainName')" # 2つのApp RunnerサービスのARNを取得し、変数に代入する - name : Get Service ARNs id : service-arns run : | echo "::set-output name=CURRENT_SERVICE_ARN::$(cat service-list.json | jq -r --arg domain ${{ steps.service-domains.outputs.CURRENT_SERVICE_DOMAIN }} 'select(.ServiceUrl==$domain) | .ServiceArn')" echo "::set-output name=NEXT_SERVICE_ARN::$(cat service-list.json | jq -r --arg domain ${{ steps.service-domains.outputs.NEXT_SERVICE_DOMAIN }} 'select(.ServiceUrl==$domain) | .ServiceArn')" # 2つのApp Runnerサービスの設定情報を取得し、デプロイトリガー部分を変更した上でファイルに書き込む - name : Get Service Config Files run : | aws apprunner describe-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} | jq '.Service.SourceConfiguration | .AutoDeploymentsEnabled=true' > current-service-config.json aws apprunner describe-service --service-arn ${{ steps.service-arns.outputs.NEXT_SERVICE_ARN }} | jq '.Service.SourceConfiguration | .AutoDeploymentsEnabled=false' > next-service-config.json # 指定したホストゾーンIDのRoute 53のレコードの一覧を取得し、指定したレコード名のものと、指定したレコード名の先頭に`_`を付したものの情報をファイルに書き込む # 2つのレコードの設定部分を変更した上でファイルに書き込む - name : Get Record Config File run : | aws route53 list-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --output json | jq --arg name ${{ secrets.AWS_ROUTE53_RECORD_NAME }} '.ResourceRecordSets[] | select(.Name | endswith($name+"."))' > record-list.json cat record-list.json | jq --arg domain ${{ steps.distribution-domains.outputs.CURRENT_DISTRIBUTION_DOMAIN }} 'select(.Type=="TXT") | .ResourceRecords[0].Value="\""+$domain+".\"" | {"Changes":[{"Action":"UPSERT"}+{ResourceRecordSet:.}]}' > txt-record.json cat record-list.json | jq --arg domain ${{ steps.distribution-domains.outputs.NEXT_DISTRIBUTION_DOMAIN }} 'select(.Type=="A") | .AliasTarget.DNSName=$domain+"." | {"Changes":[{"Action":"UPSERT"}+{ResourceRecordSet:.}]}' > a-record.json cat txt-record.json a-record.json | jq -s '.[0].Changes+.[1].Changes | {"Changes":.}' > record-config.json # 現在本番環境になっているApp Runnerサービスのデプロイトリガーを自動に変更する - name : Change Source Configuration of Current Service run : aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} --source-configuration file://current-service-config.json # 現在検証環境になっているApp Runnerサービスのデプロイトリガーを手動に変更する - name : Change Source Configuration of Next Service run : aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.NEXT_SERVICE_ARN }} --source-configuration file://next-service-config.json # CloudFrontディストリビューションの代替ドメイン名を付け替える - name : Replace Alias run : aws cloudfront associate-alias --target-distribution-id ${{ steps.distribution-id.outputs.NEXT_DISTRIBUTION_ID }} --alias ${{ secrets.AWS_ROUTE53_RECORD_NAME }} # Route 53のAレコードとTXTレコードのルーティング先を入れ替える - name : Change Record Targets run : aws route53 change-resource-record-sets --hosted-zone-id ${{ secrets.AWS_ROUTE53_HOSTED_ZONE_ID }} --change-batch file://record-config.json # 作成したファイルを削除する - name : Delete Temporary Files if : ${{ always() }} run : rm distribution-list.json current-distribution.json next-distribution.json service-list.json current-service-config.json next-service-config.json record-list.json txt-record.json a-record.json record-config.json 実際にBlue/Greenデプロイメントしてみる それでは、実際にBlue/Greenデプロイメントのワークフローを動作させ、環境の切り替わりを確認してみます。 現在の本番環境の状態を確認するために、 sample.example.com にアクセスします。 サンプルアプリケーションの文字列がそのまま表示されます。 現在の検証環境の状態を確認するために、 sample-distribution-green の ドメイン にアクセスします。 本番環境と同一の画面が表示されます。 server.py の8行目の Hello を こんにちは に変更して、 GitHub リポジトリ にpushします。 MESSAGE = "こんにちは, " + name + "!" sample-service-green へのデプロイが完了するのを待ってから、再度 sample-distribution-green の ドメイン にアクセスしてみます。 こんにちは に更新されているため、検証環境には先ほどpushした内容が反映されていることが分かります。 再度 sample.example.com にアクセスしてみます。 Hello のままであるため、先ほどのpushが本番環境には影響を与えていないことが分かります。 release/v1.0.0 というタグをpushします。 GitHub Actionsのワークフローが正常に完了していれば成功です。 git tag release/v1.0.0 git push origin release/v1.0.0 再度 sample.example.com にアクセスしてみます。 こんにちは に更新されています。Blue/Greenデプロイメントにより、先ほどまでの検証環境が数十秒のうちに本番環境に切り替わったことが確認できました! なお、確認は省略しますが、今回確認したBlueからGreenへの切り替えだけでなく、GreenからBlueへの切り替えも正常に動作します。 GitHub Actionsのワークフローの YAML ファイルの改善方法 GitHub Actionsのログ出力の制御 本記事にサンプルとして掲載した YAML ファイルによるワークフローを実行すると、Repository secrets以外の変数の値や AWS CLI のコマンドの実行結果が GitHub Actionsのログに出力されてしまいます。そのため、Publicな リポジトリ で実行する場合には、 ログ中での値のマスク や nul へのリダイレクトなどを利用して、ログの出力を工夫する必要があります。 # 値のマスクの例 CURRENT_DISTRIBUTION_DOMAIN=$(cat current-distribution.json | jq -r '.DomainName') echo "::add-mask::$CURRENT_DISTRIBUTION_DOMAIN" echo "::set-output name=CURRENT_DISTRIBUTION_DOMAIN::$CURRENT_DISTRIBUTION_DOMAIN" # nulへのリダイレクトの例 aws apprunner update-service --service-arn ${{ steps.service-arns.outputs.CURRENT_SERVICE_ARN }} --source-configuration file://current-service-config.json > nul OpenID Connectを利用した AWS リソースへのアクセス GitHub Actionsから AWS リソースにアクセスする方法に関しては、 OpenID Connectを利用する方法が先日発表されました。こちらの方法を利用すると、 AWS アクセスキーIDや AWS シークレットアクセスキーを利用せずに AWS リソースにアクセスできるため、より安全性を高められます。詳しくは、 公式ドキュメント や、テックブログ記事である OpenID Connectを利用してGitHub ActionsからAWSリソースにアクセスする を参照ください。 CloudFront ディストリビューション のキャッシュパージ Blue/Greenデプロイメントのワークフローの YAML ファイルとは別に、 main ブランチへのpushをトリガーとして検証環境側のCloudFront ディストリビューション のキャッシュをパージするワークフローの YAML ファイルを作成することにより、アプリケーションをデプロイし直す際にキャッシュによって古い情報が配信されることを防げます。 # キャッシュパージの例 NEXT_DISTRIBUTION_ID=$(aws cloudfront list-distributions | jq -r '.DistributionList.Items[] | select(.Comment | startswith("sample-distribution")) | select(.Aliases.Quantity==0) | .Id') aws cloudfront create-invalidation --distribution-id $NEXT_DISTRIBUTION_ID --paths "/*" おわりに 本記事では、 GitHub Actions、App Runner、CloudFront、Route 53を利用してBlue/Greenデプロイメントを実現する方法をご紹介しました。 GitHub リポジトリ に ソースコード をpushするだけで検証環境にアプリケーションがデプロイされ、タグをpushするだけでBlue/Greenデプロイメントが完了するというのは、開発活動を進めていく上で非常に便利で快適です。機会があれば皆さんもぜひお試しください! 電通国際情報サービス Advent Calendar 2021 3日目となる明日の記事は 比嘉康雄 さんの「 Geth(ゲス)はじめました 」です!お楽しみに! 最後までお読みいただき、本当にありがとうございました! 私たちは同じ事業部で共に働いていただける仲間を募集しています! みなさまのご応募、お待ちしています! フルサイクルエンジニア 執筆: @miyazawa.hibiki 、レビュー: @sato.taichi ( Shodo で執筆されました )
これは 電通国際情報サービス アドベントカレンダー の一日目の記事です。 はじめに 使っているツール Teams Shodo はてなブログ Markdownで書けること 高速に動作すること 見た目の調整にCSSやHTMLを使えること 記事を簡単にエクスポートできること まとめ はじめに みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部アドバンストテク ノロ ジー 部の佐藤太一です。 ISIDの技術的な取組みを外部に公開することで、ISIDをより多くのみなさんに知ってもらうためテックブログを始めました。 アドベントカレンダー の時期は、たくさんのエントリが公開されるので皆さんお忙しいとは思いますが、毎日新しい記事を公開予定ですので是非ごらんになってください。 使っているツール まずは、このブログを執筆するにあたって利用しているサービスやツールを紹介します。 Teams ISIDでは標準的なチャットツールとして、 Microsoft Teamsを使っています。 そこにテックブログ用のチームを作成して、参加メンバーのサポートや事務連絡を行っています。 Shodo Shodoはこういったブログエントリのような文章を執筆するために必要な機能が全部入った便利な執筆管理 SaaS です。 アイディアのメモ書きとタスク管理と執筆用の Markdown エディター、それに加えてレビュー管理ができます。 普段の仕事でやっているように GitHub 上でtextlintを使ったCIを回しながらブログを投稿する方法も検討しましたが、仕組みが複雑になりすぎるので諦めました。 はてなブログ ブログサービスはたくさんありますが、 はてなブログ を採用した理由を説明します。 Markdown で書けること Markdown 記法を使って記事を書けることは個人的にかなり重要視しています。 テックブログですから何かを比較検討することは多いと考えていますので、データをきれいに並べて見せられる テーブル記法 は非常に大切です。 また、 ソースコード をエントリの中に綺麗に記述したいのでコード記法が適切に動作することも必要です。 加えて、 # で見出しを並べておくと [:contents] で目次が簡単に作れるのも気に入っています。 高速に動作すること ブログのようなメディアはどんなに遅くとも3000ms以内にはコンテンツが読める状態で表示されていることが望ましいと考えています。いわゆる 三秒ルール ですね。 これは、読者が読みたいという気持ちのあるコンテンツであれば待ってもらえます。しかし、待ってもらえるということはコンテンツの質に対する期待が高まります。テックブログのように軽量なメディアでは、恐らくその期待を満たすのは難しいでしょう。 ウェブに関連する技術は極めて高速に変化している上に多様です。サーバだけでなく、経路のネットワーク、クライアントであるブラウザ、またブラウザの実行環境はパソコンだけでなく スマートフォン もあります。つまり、読者のコンテンツを届ける経路には多くの要素技術が濃縮されているのです。 私たちがコンテンツのホストを SaaS にお願いするのは、こういった最適化を主たる業務として実施しているサービス提供者の方が上手くやれるからです。 見た目の調整に CSS やHTMLを使えること 自社メディアの一種としてブログを運営するのですから、 ブランディング は大切なことです。 私たちが作成したコンテンツはホストしていただいているサービスに譲渡するわけではありません。また、ホストしているサービスが提供するコンテンツの一部というわけでもありません。 どれだけ簡単に使えるとしても、ビジュアルデザインがホストしているサービスの一部になってしまうようでは、企業としての ブランディング を損なうと私は考えています。 今の公開状態では私が見た目を調節しているので、それほど高度なビジュアルデザインは実施していません。それでも、ロゴやヘッダー画像、背景色などはコーポレートサイトとできるかぎり一致させています。 記事を簡単にエクスポートできること これも ブランディング の話の続きです。 私たちのメディアに対する姿勢が変われば SaaS の選定基準は当然変わります。その変化によってコンテンツを預けるサービスが変わることはあるでしょう。そういった時、それまでに作ったコンテンツを移行するのが難しいサービスを使いたくはありません。 もちろん、ウェブですからHTMLを スクレイピング すればあらゆるコンテンツは移行可能ですが、そういう強引なやり方をせずにコンテンツ移行できることが望ましいと私は考えます。 はてなブログ では AtomPub に対応しています。加えて、 MovableType 形式のテキストファイルによるエクスポートもあります。 まとめ この記事では主にテックブログのコンテンツをホストするためのサービスとして はてなブログ を採用した理由を説明しました。 Shodoは便利に使っているのですが、まだ使い始めたばかりで勝手が分からない部分もあり、今回は軽い説明になりました。 ISIDの アドベントカレンダー は今日始まったばかりですが、このまま25日まで続きますので、皆さんお楽しみに! 執筆: @sato.taichi 、レビュー: @handa.kenta ( Shodo で執筆されました )
これは 電通国際情報サービス アドベントカレンダー の一日目の記事です。 はじめに 使っているツール Teams Shodo はてなブログ Markdownで書けること 高速に動作すること 見た目の調整にCSSやHTMLを使えること 記事を簡単にエクスポートできること まとめ はじめに みなさんこんにちは、 電通国際情報サービス (ISID)X イノベーション 本部アドバンストテク ノロ ジー 部の佐藤太一です。 ISIDの技術的な取組みを外部に公開することで、ISIDをより多くのみなさんに知ってもらうためテックブログを始めました。 アドベントカレンダー の時期は、たくさんのエントリが公開されるので皆さんお忙しいとは思いますが、毎日新しい記事を公開予定ですので是非ごらんになってください。 使っているツール まずは、このブログを執筆するにあたって利用しているサービスやツールを紹介します。 Teams ISIDでは標準的なチャットツールとして、 Microsoft Teamsを使っています。 そこにテックブログ用のチームを作成して、参加メンバーのサポートや事務連絡を行っています。 Shodo Shodoはこういったブログエントリのような文章を執筆するために必要な機能が全部入った便利な執筆管理 SaaS です。 アイディアのメモ書きとタスク管理と執筆用の Markdown エディター、それに加えてレビュー管理ができます。 普段の仕事でやっているように GitHub 上でtextlintを使ったCIを回しながらブログを投稿する方法も検討しましたが、仕組みが複雑になりすぎるので諦めました。 はてなブログ ブログサービスはたくさんありますが、 はてなブログ を採用した理由を説明します。 Markdown で書けること Markdown 記法を使って記事を書けることは個人的にかなり重要視しています。 テックブログですから何かを比較検討することは多いと考えていますので、データをきれいに並べて見せられる テーブル記法 は非常に大切です。 また、 ソースコード をエントリの中に綺麗に記述したいのでコード記法が適切に動作することも必要です。 加えて、 # で見出しを並べておくと [:contents] で目次が簡単に作れるのも気に入っています。 高速に動作すること ブログのようなメディアはどんなに遅くとも3000ms以内にはコンテンツが読める状態で表示されていることが望ましいと考えています。いわゆる 三秒ルール ですね。 これは、読者が読みたいという気持ちのあるコンテンツであれば待ってもらえます。しかし、待ってもらえるということはコンテンツの質に対する期待が高まります。テックブログのように軽量なメディアでは、恐らくその期待を満たすのは難しいでしょう。 ウェブに関連する技術は極めて高速に変化している上に多様です。サーバだけでなく、経路のネットワーク、クライアントであるブラウザ、またブラウザの実行環境はパソコンだけでなく スマートフォン もあります。つまり、読者のコンテンツを届ける経路には多くの要素技術が濃縮されているのです。 私たちがコンテンツのホストを SaaS にお願いするのは、こういった最適化を主たる業務として実施しているサービス提供者の方が上手くやれるからです。 見た目の調整に CSS やHTMLを使えること 自社メディアの一種としてブログを運営するのですから、 ブランディング は大切なことです。 私たちが作成したコンテンツはホストしていただいているサービスに譲渡するわけではありません。また、ホストしているサービスが提供するコンテンツの一部というわけでもありません。 どれだけ簡単に使えるとしても、ビジュアルデザインがホストしているサービスの一部になってしまうようでは、企業としての ブランディング を損なうと私は考えています。 今の公開状態では私が見た目を調節しているので、それほど高度なビジュアルデザインは実施していません。それでも、ロゴやヘッダー画像、背景色などはコーポレートサイトとできるかぎり一致させています。 記事を簡単にエクスポートできること これも ブランディング の話の続きです。 私たちのメディアに対する姿勢が変われば SaaS の選定基準は当然変わります。その変化によってコンテンツを預けるサービスが変わることはあるでしょう。そういった時、それまでに作ったコンテンツを移行するのが難しいサービスを使いたくはありません。 もちろん、ウェブですからHTMLを スクレイピング すればあらゆるコンテンツは移行可能ですが、そういう強引なやり方をせずにコンテンツ移行できることが望ましいと私は考えます。 はてなブログ では AtomPub に対応しています。加えて、 MovableType 形式のテキストファイルによるエクスポートもあります。 まとめ この記事では主にテックブログのコンテンツをホストするためのサービスとして はてなブログ を採用した理由を説明しました。 Shodoは便利に使っているのですが、まだ使い始めたばかりで勝手が分からない部分もあり、今回は軽い説明になりました。 ISIDの アドベントカレンダー は今日始まったばかりですが、このまま25日まで続きますので、皆さんお楽しみに! 執筆: @sato.taichi 、レビュー: @handa.kenta ( Shodo で執筆されました )
皆さん、こんにちは。 電通国際情報サービス (以下、ISID)X イノベーション 本部アドバンストテク ノロ ジー 部の宮原です。 本日よりISIDのテックブログが開始されます。そして、本記事はISIDテックブログの記念すべき第一号です。 また、12月からはAdvent Calendarを実施していく予定です。Advent Calendarのリンクは以下にあります。 https://adventar.org/calendars/6576 こちらもぜひ楽しみにしていてください。 さて、第一号の本記事では GitHub Actionsの新機能についてご紹介します。 10/27に GitHub Blogにて以下のアナウンスがありました。 https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/ ブログのタイトルは GitHub Actions: Secure cloud deployments with OpenID Connectとなっています。このアナウンスによって、 OpenID Connectを利用し GitHub Actionsから クラウド リソースにアクセスできるようになりました。 本記事では OpenID Connectを利用し、 GitHub Actionsから AWS リソースへアクセスする方法についてご紹介します。 以下では OpenID ConnectをOIDCと略します。 概要 方法 IAM OIDC ID プロバイダーを作成する IAM ロールを作成する GitHub Actionsから一時的な認証情報を取得する まとめ 参考にした情報 概要 OIDCを利用した クラウド リソースへのアクセスについては以下のドキュメントを参考にすると良いでしょう。 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect 以下は公式ドキュメントの画像です。 クラウド プロバイダーにてロールと GitHub Actionsジョブ間のOIDCの信頼関係を設定する。 GitHub Actionsのジョブが実行されるたび、 GitHub のOIDCプロバイダーはOIDC トーク ンを自動生成する。 GitHub のOIDCプロバイダーからOIDC トーク ンを取得し、 クラウド プロバイダーに送信する。 クラウド プロバイダーはOIDC トーク ンの検証に成功したら、ジョブ内でのみ有効な、一時的な認証 トーク ンを提供する。 このような流れで一時的な認証 トーク ンを入手し、さらに認証 トーク ンを用いて クラウド リソースにアクセスします。 上記の流れを実現するために、 AWS では、 Web IDフェデレーション の機能を利用します。Web IDフェデレーションを利用し、最終的にはIAMロールを マッピング した一時的な認証 トーク ンを提供します。 方法 ここからは、OIDCを利用し GitHub Actionsから AWS リソースへアクセスする方法について記述します。 今回は、S3へのファイルアップロードを行う GitHub Actionsジョブを作成します。 AWS の各設定は全て GUI から行いました。 IAM OIDC ID プロバイダーを作成する まずは、 AWS 上でOIDC IDプロバイダーを作成します。これは GitHub OIDCプロバイダーの情報を AWS に登録する作業です。 AWS コンソールのIAM、IDプロバイダーの作成からIDプロバイダーを作成します。 OIDC IDプロバイダーの設定情報は以下のドキュメントに載っています。 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services ドキュメントに則り、OIDC IDプロバイダーの設定情報として以下を入力しました。 プロバイダーのURL: https://token.actions.githubusercontent.com 対象者: sts .amazonaws.com aws-actions/configure-aws-credentials を利用する場合は対象者の項目に sts .amazonaws.comを入力します。 IAM ロールを作成する 次にIAM ロールを作成します。コンソールからIAM、ロール、ロールを作成を選択し、ロールを作成します。 今回はWeb ID フェデレーションの機能を利用するため、信頼されたエンティティの種類にウェブIDを選択します。 IDプロバイダー、Audienceには先ほど作成した、IDプロバイダーの情報を入力します。 アクセス権限はGitHubActionsで実施したい作業に応じて設定します。 今回はS3へのファイルアップロードを実施するため、特定の バケット へのPutObjectを許可するポリシーを持つ、IAMロールを作成しました。 IAMロールを作成した後に信頼関係の修正を行います。特定の GitHub リポジトリ からのみのアクセスに制限する設定を追加します。 この作業は必ず実施してください。 制限を追加しないと、全ての リポジトリ の GitHub Actions上からIAMを利用できてしまいます。 信頼関係を修正するために、作成したIAMロールを選択し、信頼関係のタブから信頼関係の編集を選択します。 エディターが開かれるので、項目を追加します。今回はStatement.Conditionの項目に以下の条件を追加します。 これによって特定の リポジトリ からのみ、IAMを利用できます。 * の部分を詳細に記述し、ブランチの制限を加えることも可能です。 " StringLike ": { " token.actions.githubusercontent.com:sub ": " repo:<リポジトリの所有者>/<リポジトリ名>:* " } 最終的な信頼関係のポリシーは以下になります。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Principal ": { " Federated ": " arn:aws:iam::<AWSのアカウントID>:oidc-provider/token.actions.githubusercontent.com " } , " Action ": " sts:AssumeRoleWithWebIdentity ", " Condition ": { " StringEquals ": { " token.actions.githubusercontent.com:aud ": " sts.amazonaws.com " } , " StringLike ": { " token.actions.githubusercontent.com:sub ": " repo:<リポジトリの所有者>/<リポジトリ名>:* " } } } ] } aws -actions/configure- aws -credentialsに IAMロール作成のCloudFormation Template があります。こちらを利用してIAMロールを作成するのも良いでしょう。 GitHub Actionsから一時的な認証情報を取得する 最後に GitHub Actionsの設定です。 GitHub Actionsの YAML ファイルを記述します。今回は以下の YAML ファイルを作成しました。 name : Upload File on : push jobs : upload : runs-on : ubuntu-latest permissions : id-token : write contents : read steps : - uses : actions/checkout@v2 - uses : aws-actions/configure-aws-credentials@master with : role-to-assume : ${{ secrets.AWS_IAM_ROLE_ARN }} role-session-name : github-actions-sample-session aws-region : ap-northeast-1 - run : aws s3 cp ./test.txt s3://${{ secrets.AWS_S3_BUCKET_NAME }}/ GitHub Actionsのジョブ内でOIDC トーク ンを取得するにはpermissionsのid-tokenの権限をwriteにする必要があります。 IAMロールのARNは リテラル で書くこともできますが、できる限り GitHub ActionsのSecretに記述したほうが良いでしょう。 IAMロールのARNが漏えいし、先程の信頼関係が適切に設定されていないと、不正利用されてしまいます。 また、利用する aws -actions/configure- aws -credentialsのバージョンをv1ではなく、masterにしています。v1のリリースにはOIDCの機能が含まれていないため、masterブランチを指定しないと動作しません。こちらも注意するポイントです。 この設定で、特定のS3 バケット へのファイルアップロードが可能になりました。 まとめ 本記事では GitHub ActionsのOIDC機能を利用し、 GitHub Actionsから AWS リソースへアクセスする方法について記述しました。OIDCを利用することによって、アクセスキーを利用せず AWS リソースにアクセスできるようになりました。 これによってアクセスキーの漏えいのリスクを軽減できます。しかしながら、IAMロールの信頼関係を適切に設定しないと、別の不正利用にも繋がります。慎重に設定しましょう。 最後までお読みいただきありがとうございました。ISID Advent Calendarも楽しみにしていください。 参考にした情報 https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/ https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services https://github.com/aws-actions/configure-aws-credentials/blob/master/README.md 執筆: @miyahara.hikaru 、レビュー: @sato.taichi ( Shodo で執筆されました )
皆さん、こんにちは。 電通国際情報サービス (以下、ISID)X イノベーション 本部アドバンストテク ノロ ジー 部の宮原です。 本日よりISIDのテックブログが開始されます。そして、本記事はISIDテックブログの記念すべき第一号です。 また、12月からはAdvent Calendarを実施していく予定です。Advent Calendarのリンクは以下にあります。 https://adventar.org/calendars/6576 こちらもぜひ楽しみにしていてください。 さて、第一号の本記事では GitHub Actionsの新機能についてご紹介します。 10/27に GitHub Blogにて以下のアナウンスがありました。 https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/ ブログのタイトルは GitHub Actions: Secure cloud deployments with OpenID Connectとなっています。このアナウンスによって、 OpenID Connectを利用し GitHub Actionsから クラウド リソースにアクセスできるようになりました。 本記事では OpenID Connectを利用し、 GitHub Actionsから AWS リソースへアクセスする方法についてご紹介します。 以下では OpenID ConnectをOIDCと略します。 概要 方法 IAM OIDC ID プロバイダーを作成する IAM ロールを作成する GitHub Actionsから一時的な認証情報を取得する まとめ 参考にした情報 概要 OIDCを利用した クラウド リソースへのアクセスについては以下のドキュメントを参考にすると良いでしょう。 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect 以下は公式ドキュメントの画像です。 クラウド プロバイダーにてロールと GitHub Actionsジョブ間のOIDCの信頼関係を設定する。 GitHub Actionsのジョブが実行されるたび、 GitHub のOIDCプロバイダーはOIDC トーク ンを自動生成する。 GitHub のOIDCプロバイダーからOIDC トーク ンを取得し、 クラウド プロバイダーに送信する。 クラウド プロバイダーはOIDC トーク ンの検証に成功したら、ジョブ内でのみ有効な、一時的な認証 トーク ンを提供する。 このような流れで一時的な認証 トーク ンを入手し、さらに認証 トーク ンを用いて クラウド リソースにアクセスします。 上記の流れを実現するために、 AWS では、 Web IDフェデレーション の機能を利用します。Web IDフェデレーションを利用し、最終的にはIAMロールを マッピング した一時的な認証 トーク ンを提供します。 方法 ここからは、OIDCを利用し GitHub Actionsから AWS リソースへアクセスする方法について記述します。 今回は、S3へのファイルアップロードを行う GitHub Actionsジョブを作成します。 AWS の各設定は全て GUI から行いました。 IAM OIDC ID プロバイダーを作成する まずは、 AWS 上でOIDC IDプロバイダーを作成します。これは GitHub OIDCプロバイダーの情報を AWS に登録する作業です。 AWS コンソールのIAM、IDプロバイダーの作成からIDプロバイダーを作成します。 OIDC IDプロバイダーの設定情報は以下のドキュメントに載っています。 https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services ドキュメントに則り、OIDC IDプロバイダーの設定情報として以下を入力しました。 プロバイダーのURL: https://token.actions.githubusercontent.com 対象者: sts .amazonaws.com aws-actions/configure-aws-credentials を利用する場合は対象者の項目に sts .amazonaws.comを入力します。 IAM ロールを作成する 次にIAM ロールを作成します。コンソールからIAM、ロール、ロールを作成を選択し、ロールを作成します。 今回はWeb ID フェデレーションの機能を利用するため、信頼されたエンティティの種類にウェブIDを選択します。 IDプロバイダー、Audienceには先ほど作成した、IDプロバイダーの情報を入力します。 アクセス権限はGitHubActionsで実施したい作業に応じて設定します。 今回はS3へのファイルアップロードを実施するため、特定の バケット へのPutObjectを許可するポリシーを持つ、IAMロールを作成しました。 IAMロールを作成した後に信頼関係の修正を行います。特定の GitHub リポジトリ からのみのアクセスに制限する設定を追加します。 この作業は必ず実施してください。 制限を追加しないと、全ての リポジトリ の GitHub Actions上からIAMを利用できてしまいます。 信頼関係を修正するために、作成したIAMロールを選択し、信頼関係のタブから信頼関係の編集を選択します。 エディターが開かれるので、項目を追加します。今回はStatement.Conditionの項目に以下の条件を追加します。 これによって特定の リポジトリ からのみ、IAMを利用できます。 * の部分を詳細に記述し、ブランチの制限を加えることも可能です。 " StringLike ": { " token.actions.githubusercontent.com:sub ": " repo:<リポジトリの所有者>/<リポジトリ名>:* " } 最終的な信頼関係のポリシーは以下になります。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Principal ": { " Federated ": " arn:aws:iam::<AWSのアカウントID>:oidc-provider/token.actions.githubusercontent.com " } , " Action ": " sts:AssumeRoleWithWebIdentity ", " Condition ": { " StringEquals ": { " token.actions.githubusercontent.com:aud ": " sts.amazonaws.com " } , " StringLike ": { " token.actions.githubusercontent.com:sub ": " repo:<リポジトリの所有者>/<リポジトリ名>:* " } } } ] } aws -actions/configure- aws -credentialsに IAMロール作成のCloudFormation Template があります。こちらを利用してIAMロールを作成するのも良いでしょう。 GitHub Actionsから一時的な認証情報を取得する 最後に GitHub Actionsの設定です。 GitHub Actionsの YAML ファイルを記述します。今回は以下の YAML ファイルを作成しました。 name : Upload File on : push jobs : upload : runs-on : ubuntu-latest permissions : id-token : write contents : read steps : - uses : actions/checkout@v2 - uses : aws-actions/configure-aws-credentials@master with : role-to-assume : ${{ secrets.AWS_IAM_ROLE_ARN }} role-session-name : github-actions-sample-session aws-region : ap-northeast-1 - run : aws s3 cp ./test.txt s3://${{ secrets.AWS_S3_BUCKET_NAME }}/ GitHub Actionsのジョブ内でOIDC トーク ンを取得するにはpermissionsのid-tokenの権限をwriteにする必要があります。 IAMロールのARNは リテラル で書くこともできますが、できる限り GitHub ActionsのSecretに記述したほうが良いでしょう。 IAMロールのARNが漏えいし、先程の信頼関係が適切に設定されていないと、不正利用されてしまいます。 また、利用する aws -actions/configure- aws -credentialsのバージョンをv1ではなく、masterにしています。v1のリリースにはOIDCの機能が含まれていないため、masterブランチを指定しないと動作しません。こちらも注意するポイントです。 この設定で、特定のS3 バケット へのファイルアップロードが可能になりました。 まとめ 本記事では GitHub ActionsのOIDC機能を利用し、 GitHub Actionsから AWS リソースへアクセスする方法について記述しました。OIDCを利用することによって、アクセスキーを利用せず AWS リソースにアクセスできるようになりました。 これによってアクセスキーの漏えいのリスクを軽減できます。しかしながら、IAMロールの信頼関係を適切に設定しないと、別の不正利用にも繋がります。慎重に設定しましょう。 最後までお読みいただきありがとうございました。ISID Advent Calendarも楽しみにしていください。 参考にした情報 https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/ https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services https://github.com/aws-actions/configure-aws-credentials/blob/master/README.md 執筆: @miyahara.hikaru 、レビュー: @sato.taichi ( Shodo で執筆されました )