TECH PLAY

電通総研

電通総研 の技術ブログ

822

電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、Stable Diffusion 2.0-美少女イラストです。 Stable Diffusionも2.0になったので、もう一度検証し直します。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 長い呪文は切り捨てられる編 AUTOMATIC 1111のインストール AUTOMATIC 1111のセッティング 呪文の基本ルール 美少女イラストの呪文 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render v1.5 まとめ 仲間募集 Stable Diffusionの全コンテンツ AUTOMATIC 1111のインストール Stable Diffusion 2.0 を実行する環境として、 AUTOMATIC 1111 を使います。 AUTOMATIC 1111 は、 Stable Diffusion を Google Colab 直接ではなく、 UI 経由で実行できるようにしています。似たような機能のものはいくつかありますが、 AUTOMATIC 1111 が最も人気があるので、今回からこれを使います。 Google Colab で動かすためのノートブックは こちら になります。 このノートブックを実行すると、下の方に Running on public URL: https://2c76db068be0e79a.gradio.app のようなURLのリンクが表示されるので、クリックしましょう。 Colab Pro の通常メモリでは、メモリが足りなくなることがありました。そのような場合は、 Google Drive にコピーしてハイメモリにしましょう。 AUTOMATIC 1111のセッティング パラメータは以下のようになります。 Sampling Steps: 50 Sampling Method: DPM2 Width: 768 Height: 768 CFG Scale: 7.5 Seed: -1 Sampling Stepsは、デフォルトの 20 だと少ないので、 50 くらいにしておきましょう。 Sampling Methodは DPM2 をお勧めしますが、 DDIM も悪くはありません。それ以外は、描く対象によりますが、あまりお勧めしません。 Width と Height は 768 をお勧めします。 Stable Diffusion 2.0 のモデルの 768-v-ema.ckpt は、 768 用のためです。 512 だとかを使うと画像が崩れることがあります。 CFG Scale は入力した呪文にどれだけ近い画像を生成するかのパラメータです。デフォルトの 7 でも問題ありませんが、僕はなんとなく 7.5 を指定しています。 呪文の基本ルール 以前よりも僕自身、呪文(prompt)の基本ルールがわかってきたので、お伝えします。経験則的な側面が強いですが、たぶん、あっていると思います。 トーク ンは75まで。 カンマ(,)は必要がない。カンマも トーク ンの一つとしてカウントされるので、ないほうが良い。 前の方にある トーク ンの方が出力結果に与える影響が大きい。 冠詞(a, an, theなど)は必要がない。 近くにある トーク ンは影響を受ける。後ろにある トーク ンも前の トーク ンに影響を与える。 トーク ンと トーク ンの間の トーク ン数が多くなると、 トーク ンの影響は少なくなる。 呪文は トーク ンに分解されます。 トーク ンは基本的には、単語だと思って大丈夫ですが、 Stable Diffusion が知らない トーク ンは、一つの単語が複数の トーク ンに分解されることもあります。例えば、 pixiv は、 pi と xiv の2つの トーク ンになります。 「後ろにある トーク ンも前の トーク ンに影響を与える」というのは、これまで、なんとなく感じていたけど、 言語化 できていなかった部分ではないでしょうか。 例えば、 apple on table だと赤いリンゴがほとんどです。しかし、 apple on yellow table だと黄色のりんごになる確率が増えます。 トーク ンと トーク ンの間の トーク ン数が多くなると、 トーク ンの影響は少なくなります。 例えば、 apple on yellow table だと黄色になるリンゴもありますが、 apple on a a a a a a a a a a a a a a a a a a yellow table だと、黄色になるリンゴの確率はかなり減ります。 美少女イラストの呪文 それでは、呪文の基本ルールをふまえた、 Stable Diffusion 2.0 で検証済みの美少女イラストの呪文を紹介します。 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render 閲覧用改行版 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render 出力結果の例です。 これは、厳選したものではなく、連発できます。それでは、呪文の中身を解説しましょう。 beautiful princess これまで、美少女を描画するときは、 beautiful girl を指定していたのですが、これを beautiful princess に変えました。これがかなり効果的で、美少女になる確率がかなり高くなりました。 beautiful face 美少女率を上げるためには、 beautiful face はあったほうが良いです。 beautiful hair beautiful clothes この呪文は、それほど大した意味はないのですが、前の face と次の artstation との距離をあけるために指定しています。 artstation はアートの投稿サイトなので、 face と artstation が近くにあると、 face に過剰に色がつくなど artstation の影響をうけることがあります。 hair と clothes は、 artstation の影響を受けてもそれほど問題はないので、緩衝材にぴったりです。 artstation artstation は、 art (イラスト)の投稿サイトです。 artstation を指定することで、出力結果がイラストになります。 また、 artstation を指定することで、出力される画像のクオリティが上がります。 fantasy scene fantasy composition fantasy lighting シーン(scene)、構図(composition)、ライティング(lighting)は指定しておきましょう。指定しないと人物だけの単純な画像になることがあります。 修飾語は好みで構いません。 fantasy は美少女と相性の良い修飾語です。 princess 、 face 、 hair 、 clothes などに悪影響を与えることもありません。 octane render octane render を指定すると画像が多少立体的になります。二次元が好きな方は外してください。 v1.5 v1.5で下記のパラメータで実行してみました。 Width と Height を 512 に変えただけです。 Sampling Steps: 50 Sampling Method: DPM2 Width: 512 Height: 512 CFG Scale: 7.5 Seed: -1 v1.5でも、良い結果が連発できました。呪文が改善されたことと、 Sampling Method を DDIM から DPM2 にしたことが良い結果につながったのかもしれません。 比べてみるとv2.0のほうが、質感が増しているのがわかります。 v1.5の出力結果はこちら。 まとめ 今回は、 Stable Diffusion 2.0 の美少女イラストの呪文を紹介しました。 v1.4 と v1.5 の違いはあまりなかったのですが、 v2.0 では画像の質感が増し、明らかに良くなったと思います。みなさんもぜひ試してください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの全コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回は、Stable Diffusion 2.0-美少女イラストです。 Stable Diffusionも2.0になったので、もう一度検証し直します。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文編 美少女アニメ画編 美少女写真編 女性イラスト編 長い呪文は切り捨てられる編 AUTOMATIC 1111のインストール AUTOMATIC 1111のセッティング 呪文の基本ルール 美少女イラストの呪文 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render v1.5 まとめ 仲間募集 Stable Diffusionの全コンテンツ AUTOMATIC 1111のインストール Stable Diffusion 2.0 を実行する環境として、 AUTOMATIC 1111 を使います。 AUTOMATIC 1111 は、 Stable Diffusion を Google Colab 直接ではなく、 UI 経由で実行できるようにしています。似たような機能のものはいくつかありますが、 AUTOMATIC 1111 が最も人気があるので、今回からこれを使います。 Google Colab で動かすためのノートブックは こちら になります。 このノートブックを実行すると、下の方に Running on public URL: https://2c76db068be0e79a.gradio.app のようなURLのリンクが表示されるので、クリックしましょう。 Colab Pro の通常メモリでは、メモリが足りなくなることがありました。そのような場合は、 Google Drive にコピーしてハイメモリにしましょう。 AUTOMATIC 1111のセッティング パラメータは以下のようになります。 Sampling Steps: 50 Sampling Method: DPM2 Width: 768 Height: 768 CFG Scale: 7.5 Seed: -1 Sampling Stepsは、デフォルトの 20 だと少ないので、 50 くらいにしておきましょう。 Sampling Methodは DPM2 をお勧めしますが、 DDIM も悪くはありません。それ以外は、描く対象によりますが、あまりお勧めしません。 Width と Height は 768 をお勧めします。 Stable Diffusion 2.0 のモデルの 768-v-ema.ckpt は、 768 用のためです。 512 だとかを使うと画像が崩れることがあります。 CFG Scale は入力した呪文にどれだけ近い画像を生成するかのパラメータです。デフォルトの 7 でも問題ありませんが、僕はなんとなく 7.5 を指定しています。 呪文の基本ルール 以前よりも僕自身、呪文(prompt)の基本ルールがわかってきたので、お伝えします。経験則的な側面が強いですが、たぶん、あっていると思います。 トーク ンは75まで。 カンマ(,)は必要がない。カンマも トーク ンの一つとしてカウントされるので、ないほうが良い。 前の方にある トーク ンの方が出力結果に与える影響が大きい。 冠詞(a, an, theなど)は必要がない。 近くにある トーク ンは影響を受ける。後ろにある トーク ンも前の トーク ンに影響を与える。 トーク ンと トーク ンの間の トーク ン数が多くなると、 トーク ンの影響は少なくなる。 呪文は トーク ンに分解されます。 トーク ンは基本的には、単語だと思って大丈夫ですが、 Stable Diffusion が知らない トーク ンは、一つの単語が複数の トーク ンに分解されることもあります。例えば、 pixiv は、 pi と xiv の2つの トーク ンになります。 「後ろにある トーク ンも前の トーク ンに影響を与える」というのは、これまで、なんとなく感じていたけど、 言語化 できていなかった部分ではないでしょうか。 例えば、 apple on table だと赤いリンゴがほとんどです。しかし、 apple on yellow table だと黄色のりんごになる確率が増えます。 トーク ンと トーク ンの間の トーク ン数が多くなると、 トーク ンの影響は少なくなります。 例えば、 apple on yellow table だと黄色になるリンゴもありますが、 apple on a a a a a a a a a a a a a a a a a a yellow table だと、黄色になるリンゴの確率はかなり減ります。 美少女イラストの呪文 それでは、呪文の基本ルールをふまえた、 Stable Diffusion 2.0 で検証済みの美少女イラストの呪文を紹介します。 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render 閲覧用改行版 beautiful princess beautiful face beautiful hair beautiful clothes artstation fantasy scene fantasy composition fantasy lighting octane render 出力結果の例です。 これは、厳選したものではなく、連発できます。それでは、呪文の中身を解説しましょう。 beautiful princess これまで、美少女を描画するときは、 beautiful girl を指定していたのですが、これを beautiful princess に変えました。これがかなり効果的で、美少女になる確率がかなり高くなりました。 beautiful face 美少女率を上げるためには、 beautiful face はあったほうが良いです。 beautiful hair beautiful clothes この呪文は、それほど大した意味はないのですが、前の face と次の artstation との距離をあけるために指定しています。 artstation はアートの投稿サイトなので、 face と artstation が近くにあると、 face に過剰に色がつくなど artstation の影響をうけることがあります。 hair と clothes は、 artstation の影響を受けてもそれほど問題はないので、緩衝材にぴったりです。 artstation artstation は、 art (イラスト)の投稿サイトです。 artstation を指定することで、出力結果がイラストになります。 また、 artstation を指定することで、出力される画像のクオリティが上がります。 fantasy scene fantasy composition fantasy lighting シーン(scene)、構図(composition)、ライティング(lighting)は指定しておきましょう。指定しないと人物だけの単純な画像になることがあります。 修飾語は好みで構いません。 fantasy は美少女と相性の良い修飾語です。 princess 、 face 、 hair 、 clothes などに悪影響を与えることもありません。 octane render octane render を指定すると画像が多少立体的になります。二次元が好きな方は外してください。 v1.5 v1.5で下記のパラメータで実行してみました。 Width と Height を 512 に変えただけです。 Sampling Steps: 50 Sampling Method: DPM2 Width: 512 Height: 512 CFG Scale: 7.5 Seed: -1 v1.5でも、良い結果が連発できました。呪文が改善されたことと、 Sampling Method を DDIM から DPM2 にしたことが良い結果につながったのかもしれません。 比べてみるとv2.0のほうが、質感が増しているのがわかります。 v1.5の出力結果はこちら。 まとめ 今回は、 Stable Diffusion 2.0 の美少女イラストの呪文を紹介しました。 v1.4 と v1.5 の違いはあまりなかったのですが、 v2.0 では画像の質感が増し、明らかに良くなったと思います。みなさんもぜひ試してください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの全コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの柴田です。 これは 電通国際情報サービス Advent Calendar 2022 6日目の記事です。5日目の昨日は、もう一人の柴田さんの記事「 Argo CDを使ってIstioをバージョンアップする 」でした。 はじめに Webアプリケーションの構成 動作イメージ コードサンプル はじめに 社内で Excel ファイルを扱う業務があり、特定の Excel ファイルをテンプレートとして都度ファイルをコピーする形で運用しています。 Excel ファイルはメンテナンス時の差分管理が Excel 単体の機能では難しく、複数人での修正も可能ですが、個人的には変更履歴があまり視認性の良いものではないと考えています。 そこで、 Excel ファイルに記載する内容を別途テキストベースで差分管理し、修正したテキストを Excel ファイルに流し込むシステムを構築することになりました。 また、併せてNext.jsを利用しWebアプリケーション化も実現して、 Excel ファイルに追加するテキストもWebアプリケーション上から入力できるような仕組みにしています。 今回作成しているWebアプリケーションは、Next.jsとTypeScriptを利用しています。 その中で、 Amazon S3 に保存した Excel ファイル(拡張子xlsx)を取得して、 Excel ファイルを加工処理してからクライアント( Webブラウザ )に返却する、という処理を紹介します。 Webアプリケーションの構成 前述の通り、 Amazon S3 を利用しているため、Webシステムは AWS 上に構築しています。 実際はもう少し複雑なシステム構成ですが、以下は、今回紹介する Excel 処理部分のフローを抜粋した図です。 Webブラウザ からリク エス トを受けたWebアプリケーション(Next.js)が、バックエンド側で Amazon S3 から Excel ファイルを取得します。取得した Excel ファイルの加工処理をした後、 Webブラウザ 側に返却する流れです。 Amazon S3 からのデータ取得のライブラリは AWS SDK for JavaScript v3 、 Excel ファイルの加工のライブラリは ExcelJS を利用しています。 動作イメージ ダウンロードボタンを押すと、 /api/download にリク エス トが送信され、加工したファイルがダウンロードされます。 コードサンプル Next.jsのプロジェクト作成は、以下の陳さんの記事を参考にしました。 tech.isid.co.jp まずはフロント側です。 // pages/index.tsx import styles from '../styles/Home.module.css' const Home = () => { const download = async ( fileName: string ) => { const response = await fetch ( `/api/download` , { method: 'GET' } ) try { if ( response. status !== 200 ) { throw new Error () } else { const blob = await response.blob () const blobFile = new Blob ( [ blob ] , { type : 'application/xlsx' } ) const a = document .createElement ( 'a' ) a.style.display = 'none' document .body.appendChild ( a ) const url = window .URL.createObjectURL ( blobFile ) a.href = url a.download = fileName a.click () window .URL.revokeObjectURL ( url ) } } catch ( e: unknown ) { console .error ( e ) } } return ( < div className = { styles.container } > < main className = { styles.main } > < button onClick = { () => download ( 'ダウンロードファイル名.xlsx' ) } > ダウンロード < /button > < /main > < /div > ) } export default Home サンプルコードでは、ボタンをクリックするとバックエンドの API にリク エス トし、レスポンスを組み立てて、ファイル保存のダイアログを表示させる形にしています。 次に API です。 // pages/api/download.ts import { Readable } from 'stream' import * as ExcelJS from 'exceljs' import type { NextApiRequest , NextApiResponse } from 'next' import { getObject } from '../../src/aws/s3' export default async function download ( req: NextApiRequest , res: NextApiResponse ) : Promise < void > { try { const s3Output = await getObject ( 'bucket' , 'key' ) if ( s3Output.Body instanceof Readable ) { const s3OutputStream = s3Output.Body as Readable res.setHeader ( 'Content-Type' , 'application/xlsx' ) res. status( 200 ) const workbook = new ExcelJS.Workbook () await workbook.xlsx.read ( s3OutputStream ) const sheet = workbook.getWorksheet ( 'Sheet1' ) sheet.getRow ( 1 ) .getCell ( 1 ) .value = 'テスト' await workbook.xlsx.write ( res ) } return } catch ( e: unknown ) { return res. status( 500 ) .json ( { message: 'Internal Server Error' } ) } } // src/aws/s3.ts import { GetObjectCommand , GetObjectCommandOutput , S3Client } from '@aws-sdk/client-s3' export async function getObject ( bucket: string , key: string ) : Promise < GetObjectCommandOutput > { const params = { Bucket: bucket , Key: key , } const command = new GetObjectCommand ( params ) return await new S3Client ( { region: 'ap-northeast-1' } ) .send ( command ) } Amazon S3 にリク エス トを送信し、レスポンスを受け取ります。 受け取ったStreamを順次処理するため、そのままExcelJSに渡しています。 ExcelJSでは、Streamをそのまま処理してくれます。 参考 github.com ExcelJSの ソースコード を見ると、実際はメモリ上に一旦すべて展開されます。 Streamのまま順次処理していく機能も用意されていますが、今回はファイルサイズが小さく、処理が複雑になりそうだったため利用しませんでした。 参考 github.com 最後にテストコードです。 API Routes のテストライブラリは next-test-api-route-handler を利用しています。 // test/api/download.test.ts import fs from 'fs' import path from 'path' import { PassThrough } from 'stream' import { GetObjectCommandOutput } from '@aws-sdk/client-s3' import { sdkStreamMixin } from '@aws-sdk/util-stream-node' import * as ExcelJS from 'exceljs' import { testApiHandler } from 'next-test-api-route-handler' import download from '../../pages/api/download' import * as s3Module from '../../src/aws/s3' const handler: typeof download = download const filePath = path.join ( __dirname , 'download.xlsx' ) const getObjectSpyOn = jest.spyOn ( s3Module , 'getObject' ) const getObject200 = async () : Promise < GetObjectCommandOutput > => { return { Body: sdkStreamMixin ( fs.createReadStream ( filePath )), $metadata: { httpStatusCode: 200 , } , } } const getObject500 = async () : Promise < GetObjectCommandOutput > => { const mockReadable = sdkStreamMixin (new PassThrough ()) mockReadable.emit ( 'error' , new Error ( 'error' )) return { Body: mockReadable , $metadata: { httpStatusCode: 500 , } , } } describe ( 'client-s3' , () => { test ( '200' , async () => { getObjectSpyOn.mockImplementation ( getObject200 ) await testApiHandler ( { handler , test: async ( { fetch } ) => { const res = await fetch ( { method: 'GET' } ) expect ( res. status) .toBe ( 200 ) const workbook = new ExcelJS.Workbook () await workbook.xlsx.read ( res.body ) const worksheet = workbook.getWorksheet ( 'Sheet1' ) expect ( worksheet.getRow ( 1 ) .getCell ( 1 ) .value ) .toBe ( 'テスト' ) } , } ) } ) test ( '500' , async () => { getObjectSpyOn.mockImplementation ( getObject500 ) await testApiHandler ( { handler , test: async ( { fetch } ) => { const res = await fetch ( { method: 'GET' } ) expect ( res. status) .toBe ( 500 ) } , } ) } ) } ) Amazon S3 から取得する箇所をモック化しています。Stream部分には PassThrough を利用しています。 また、 AWS SDK for JavaScript v3のv3.188.0以降では、Stream処理が一部変更になり、Bodyの型が SdkStream<Readable> で返るようになりました。 そのため、 @aws-sdk/util-stream-node にある sdkStreamMixin を利用して、テストコードのモックでも同じ型を返すようにしています。 参考 github.com github.com 以上、Next.jsとTypeScriptを利用して、 Amazon S3 に保存されている Excel をExcelJSで加工してクライアントに返す方法の紹介でした。 電通国際情報サービス Advent Calendar 2022 7日目の明日は、Faisalさんの記事です。お楽しみに! 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 - セキュリティエンジニア(セキュリティ設計) 執筆: @shibata.shunsuke 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
初めに ISID X(クロス) イノベーション 本部 の三浦です。 仕事でかかわってるわけではありませんが、個人的な興味としてStable Diffusion周りの動向を追っております。 あの界隈の進歩は今凄まじいですね。 で、 google colab上で試すというやり方もあるのですが、 google colabのリソース枠を使い果たす事態が最近頻発しておりますので今回はdokcerベースでローカル環境を作りました。 また、CPUのみのマシンでもお試しできることを確認しましたので執筆している次第です。 概要 Stable Diffusion、および、Stable Diffusion派生モデルを使用していくうえで非常に便利な『 Stable Diffusion web UI(AUTOMATIC1111版) 』の環境を GPU が使えないマシン向けにdokcer で構築します。 なお、Stable Diffusion web UI(AUTOMATIC1111)は、 google colab上で動作させることも可能です。 前提条件、検証環境 dokcer compose 使用 gitクライアント 使用 windows10 メモリ 16G以上 (メモリ8Gは厳しいです) 手順概要 stable-diffusion-webui-docker  を clone docker compose --profile download up --build docker compose --profile auto-cpu up --build これだけでAI絵作成用のツール、Stable Diffusion web UI(AUTOMATIC1111版)を立ち上げることが可能です。 なお、本日は省略しますが、簡易に他のStable Diffusion派生モデルを追加し利用することも可能です。 実行例 stable-diffusion-webui-docker をclone後、 docker compose --profile download up --build 上記コマンドを実行します。相当量のリソースをダウンロードするため私の環境では1h弱かかりました。 上記のように必要なリソースがダウンロードできたので次はCPUモードで起動します。 なお、上記 ディレクト リに手を加えれば他のStable Diffusion派生モデルの利用も可能です。 また、起動オプションの変更により GPU モードで起動することも簡単です。 さらにdocker-composeファイルを編集することにより低スペックな GPU でも稼働させることもできます(※1)。 docker compose --profile auto-cpu up --build 起動が完了すると↑のようにURLが表示されますので http://localhost:7860/ にアクセスします。 なお、一度目の起動には非常に時間がかかりますが、二回目以降は数分レベルで起動します。 利用例 上記設定で、下記ぐらいの速度で生成できます。 intel i7 4770s (物理4コア)の場合、400s Ryzen 7 3700X (物理8コア)の場合、200s 解像度、step数、 サンプラー 、モデルで生成速度はだいたい決まります。対話式でいろいろ試すにはややつらいですが、プロンプト掲載サイトのプロンプトを試すならある程度割り切れる速度かと思ってる感じです(※2)。 また、こんな感じで簡単に複数枚作ることもできるので寝ている間にCPUを酷使して大量生成するのも簡単です。 GPU でちゃんとモデルがのる環境だと、↓が1分程度で生成可能です。 CPUのみ物理4コアのマシンにて約40分で生成しました。100枚までまとめて生成可能なので夜数時間かけて生成するという使い方も可能です。 まとめ いま、画像生成の分野は非常に盛り上がっていると思います。ご興味を持った方は触ってみるといいのではないでしょうか? 注記 ※1 メモリ4Gクラスの GPU でも動作させることができます。GTX1650で稼働することを確認しました。が、かなり遅くなるので、メモリ8G以上の GPU がお薦めです。 ※2 google colabだと、10s-20s程度です。 執筆: @miura.toshihiko 、レビュー: @higa ( Shodo で執筆されました )
アバター
初めに ISID X(クロス) イノベーション 本部 の三浦です。 仕事でかかわってるわけではありませんが、個人的な興味としてStable Diffusion周りの動向を追っております。 あの界隈の進歩は今凄まじいですね。 で、 google colab上で試すというやり方もあるのですが、 google colabのリソース枠を使い果たす事態が最近頻発しておりますので今回はdokcerベースでローカル環境を作りました。 また、CPUのみのマシンでもお試しできることを確認しましたので執筆している次第です。 概要 Stable Diffusion、および、Stable Diffusion派生モデルを使用していくうえで非常に便利な『 Stable Diffusion web UI(AUTOMATIC1111版) 』の環境を GPU が使えないマシン向けにdokcer で構築します。 なお、Stable Diffusion web UI(AUTOMATIC1111)は、 google colab上で動作させることも可能です。 前提条件、検証環境 dokcer compose 使用 gitクライアント 使用 windows10 メモリ 16G以上 (メモリ8Gは厳しいです) 手順概要 stable-diffusion-webui-docker  を clone docker compose --profile download up --build docker compose --profile auto-cpu up --build これだけでAI絵作成用のツール、Stable Diffusion web UI(AUTOMATIC1111版)を立ち上げることが可能です。 なお、本日は省略しますが、簡易に他のStable Diffusion派生モデルを追加し利用することも可能です。 実行例 stable-diffusion-webui-docker をclone後、 docker compose --profile download up --build 上記コマンドを実行します。相当量のリソースをダウンロードするため私の環境では1h弱かかりました。 上記のように必要なリソースがダウンロードできたので次はCPUモードで起動します。 なお、上記 ディレクト リに手を加えれば他のStable Diffusion派生モデルの利用も可能です。 また、起動オプションの変更により GPU モードで起動することも簡単です。 さらにdocker-composeファイルを編集することにより低スペックな GPU でも稼働させることもできます(※1)。 docker compose --profile auto-cpu up --build 起動が完了すると↑のようにURLが表示されますので http://localhost:7860/ にアクセスします。 なお、一度目の起動には非常に時間がかかりますが、二回目以降は数分レベルで起動します。 利用例 上記設定で、下記ぐらいの速度で生成できます。 intel i7 4770s (物理4コア)の場合、400s Ryzen 7 3700X (物理8コア)の場合、200s 解像度、step数、 サンプラー 、モデルで生成速度はだいたい決まります。対話式でいろいろ試すにはややつらいですが、プロンプト掲載サイトのプロンプトを試すならある程度割り切れる速度かと思ってる感じです(※2)。 また、こんな感じで簡単に複数枚作ることもできるので寝ている間にCPUを酷使して大量生成するのも簡単です。 GPU でちゃんとモデルがのる環境だと、↓が1分程度で生成可能です。 CPUのみ物理4コアのマシンにて約40分で生成しました。100枚までまとめて生成可能なので夜数時間かけて生成するという使い方も可能です。 まとめ いま、画像生成の分野は非常に盛り上がっていると思います。ご興味を持った方は触ってみるといいのではないでしょうか? 注記 ※1 メモリ4Gクラスの GPU でも動作させることができます。GTX1650で稼働することを確認しました。が、かなり遅くなるので、メモリ8G以上の GPU がお薦めです。 ※2 google colabだと、10s-20s程度です。 執筆: @miura.toshihiko 、レビュー: @higa ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 この記事は 電通国際情報サービス Advent Calendar 2022 の5日目の投稿です。 前日の記事は宮澤さんの「お金をかけずに AWS Certified SysOps Administrator - Associateに合格した話」でした。 さて、この記事ではArgo CDを使ってIstioをバージョンアップする方法を紹介します。 はじめに Istioとは Argo CDとは Istioをどうやってバージョンアップするか Argo CDを使ってIstioをバージョンアップする 想定する環境 環境を構築する Amazon EKS Argo CD Istio サンプルアプリケーション 手順1. Canary upgradeが可能か確認する 手順2. istio/base (CRDを含む)を更新する 手順3. 新しいバージョンの istio/istiod を追加する 手順4. データプレーンを更新する 手順5. istio/base の defaultRevision を更新する 手順6. istio/gateway を更新する 手順7. 古いバージョンの istio/istiod を削除する おわりに はじめに Istioとは Istio はサービスメッシュを実現する OSS の1つです。 サービスメッシュとは、マイクロサービスにおける トラフィック 管理、可観測性、セキュリティなどを、インフラスト ラク チャレイヤーで透過的に実現する仕組みです。 サービスメッシュがどんな課題を解決するものかは、以下のスライドが参考になります。 サービスメッシュは本当に必要なのか、何を解決するのか / Service meshes - Do we really need them? What problems do they solve? - Speaker Deck Istioはデータプレーンとコン トロール プレーンの2つの コンポーネント から構成されます。 データプレーン : 実際にサービス間通信を行う部分です。 アプリケーションのプロキシとして Envoy を起動します ( サイドカー コンテナとして起動するケースが多いです)。 Envoyは トラフィック 管理、可観測性、セキュリティなどの機能を提供します。 コン トロール プレーン : Envoyの設定を管理・更新します。 1 Istioのサポートポリシーは Supported Releases に定義されています。 概要は以下のとおりです。 4半期に1回、マイナーリリースを行う。 各マイナーリリースのサポート(=セキュリティパッチの提供など)はN+2マイナーリリースの6週間後まで提供される。 例: v1.11 のサポートは v1.13.0 をリリースした6週間後まで提供される。 Argo CDとは Argo CD は Kubernetes 用の継続的デリバリーツールの1つです。 Argo CDは、git リポジトリ で管理された Kubernetes の マニフェスト の変更を、 Kubernetes クラスタ へデプロイします。 2 Argo CDは GitOps という手法に基づいています。 GitOpsはWeaveworks社が提唱した継続的デリバリー・継続的デプロイメントの方法であり、以下の特徴があります。 システム全体が宣言的に記述されていること。 マニフェスト がgitで管理され、それが信頼できる唯一の情報源(Single Source of Truth)であること。 承認された マニフェスト の変更が自動的にデプロイされること。またその際 Kubernetes クラスタ への認証情報を外部(例えばCIサーバなど)に持たせる必要がないこと。 マニフェスト と Kubernetes クラスタ の間に差分がある場合、それを検知したり、自動的に修正したりできること。 Istioをどうやってバージョンアップするか 先述した通りIstioの各マイナーリリースはリリースから約6〜8ヶ月後にサポートが終了します。 そのため定期的にIstioをバージョンアップする必要があります。 公式ドキュメント には以下の3つのバージョンアップ方法が紹介されています。 istioctl を使ってIn-place upgradeする方法 istioctl を使ってCanary upgradeする方法 Helmを使ってCanary upgradeする方法(α版) なお、In-place upgradeの場合、バージョンアップ前後のバージョン差異は1マイナーリリース以内である必要があります。 3 また、Canary upgradeの場合、バージョンアップ前後のバージョン差異は2マイナーリリース以内であることが推奨されています。 4 実案件の多くは、 Kubernetes マニフェスト のデプロイを、Argo CDなどの継続的デリバリーツールを使って行います。 しかし、上述のドキュメントでは、 Kubernetes クラスタ 管理者が手元からコマンドを実行してバージョンアップする方法しか紹介されていません。 そこで本記事では、Argo CDを使ってIstioをバージョンアップする方法を紹介します。 今回は以下の理由から方法3をベースとします。 公式ドキュメントではIn-place upgradeよりもCanary upgradeを推奨しているため。 5 Argo CDをはじめ 6 、多くの継続的デリバリーツールがHelmをサポートしているため。 Argo CDを使ってIstioをバージョンアップする 想定する環境 本記事では以下の環境を前提とします。 Amazon EKS v1.23 Argo CD v2.4.14 Istio v1.13.4 → v1.15.3 またサンプルアプリケーションとしてIstio公式のサンプルアプリケーションである Bookinfoアプリケーション がデプロイされているものとします。 環境を構築する Amazon EKS Amazon EKS クラスタ を構築します。 本記事では詳細な手順は割愛します。 詳しくは Amazon EKS クラスターの作成 - Amazon EKS をご参照ください。 Argo CD Kubernetes クラスタ へArgo CDをデプロイします。 本記事では詳細な手順は割愛します。 詳しくは以下の公式ドキュメントをご参照ください。 Getting Started - Argo CD - Declarative GitOps CD for Kubernetes Installation - Argo CD - Declarative GitOps CD for Kubernetes Istio IstioのHelm chartは以下の3つを使用します。 istio/base istio/istiod istio/gateway 本記事では以下の手順で Kubernetes クラスタ にIstio v1.13.4がインストールされているものとします。 必要なnamespaceを作成します。 $ kubectl create namespace istio-system $ kubectl create namespace istio-ingress istio-ingress namespaceに automatic sidecar injection を有効にするためのlabelを設定します。 $ kubectl label namespace istio-ingress istio-injection=enabled 以下のArgo CDのApplication CRを kubectl apply コマンドでデプロイします。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.13.4 helm : releaseName : istio-base destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy --- apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.13.4 helm : releaseName : istiod destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle - /webhooks/2/clientConfig/caBundle - /webhooks/3/clientConfig/caBundle --- apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-ingress namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : gateway targetRevision : 1.13.4 helm : releaseName : istio-ingress destination : server : https://kubernetes.default.svc namespace : istio-ingress 上述のApplication CRについてArgo CDのWeb UIからsyncを実行してIstioをデプロイします。 syncは istio-base → istiod → istio-ingress の順に実行します。 本記事ではArgo CDのWeb UIのアクセス方法および操作方法に関する説明は割愛します。詳細は Argo CDの公式ドキュメント をご参照ください。 サンプルアプリケーション 本記事ではサンプルアプリケーションとして default namespaceにIstio公式のサンプルアプリケーションである Bookinfoアプリケーション がデプロイされているものとします。 default namespaceに automatic sidecar injection を有効にするためのlabelを設定します。 $ kubectl label namespace default istio-injection=enabled Bookinfoアプリケーション をデプロイします。 $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/platform/kube/bookinfo.yaml $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/networking/bookinfo-gateway.yaml $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/networking/destination-rule-all.yaml Gateway bookinfo-gateway の .spec.selector を istio=ingressgateway から istio=ingress へ変更します。 $ kubectl patch gateways.networking.istio.io bookinfo-gateway --type=merge -p '{"spec":{"selector":{"istio":"ingress"}}}' 手順1. Canary upgradeが可能か確認する 以下の条件を満たしているか確認します。 Istioのバージョンアップ前後のバージョン差異が2マイナーリリース以内であること。 例えば、現在のバージョンが 1.13.x の場合、 1.14.x と 1.15.x へバージョンアップできます。 istio/base 、 istio/gateway 、CRDに破壊的変更がないこと。 これらのリソースはバージョンアップ前後で共有されるためです。 istio/istiod がバージョンアップ前後で独立しており同じリソースを共有しないこと。 古いバージョンの istio/istiod を安全に削除するためです。 なお istio/istiod のパラメータ revision にはバージョンアップ前後で異なる値を設定します。 kubectl rollout restart コマンドでアプリケーションのPodを安全に再作成できること。 PodがGraceful Shutdownするよう実装・設定されているか。 PodのUpdate strategyが適切に設定されているか。 以下の手順でバージョンアップ前後の互換性が確認できていること。 更新後のバージョンの istioctl を GitHubのリリース からダウンロードする。 istioctl x precheck コマンドを実行して互換性を確認する。 $ istioctl x precheck ✔ No issues found when checking the cluster. Istio is safe to install or upgrade! To get started, check out <https://istio.io/latest/docs/setup/getting-started/> 手順2. istio/base (CRDを含む)を更新する Application CR istio-base の targetRevision を更新します。これによりCRDなどが更新されます。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.15.3 helm : releaseName : istio-base destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy 本来、 helm upgrade コマンドや helm uninstall コマンドでhelm chartを更新/削除しても、CRDは更新/削除されません。 7 ただし、Argo CDは helm template で生成した マニフェスト を kubectl apply しているため、Helm chartを更新することで、CRDを更新できます。 手順3. 新しいバージョンの istio/istiod を追加する 新しいバージョンの istio/istiod のApplication CRをデプロイします。 このとき、以下のフィールドは更新後のバージョンにあわせて変更します。 .metadata.name .spec.source.targetRevision .spec.source.helm.values の revision (*1) .spec.ignoreDifferences 8 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod-1-15-3 namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.15.3 helm : releaseName : istiod values : | revision : "1-15-3" destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector-1-15-3 jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle istio-system namespaceに新しいバージョンの istiod が作成されたことを確認します。 $ kubectl -n istio-system get pods,svc,mutatingwebhookconfigurations NAME READY STATUS RESTARTS AGE pod/istiod-1-15-3-59d4cf9474-gklrs 1/1 Running 0 11s pod/istiod-579df55f96-n4tk8 1/1 Running 0 21m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istiod ClusterIP 172.20.91.227 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 21m service/istiod-1-15-3 ClusterIP 172.20.213.98 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 11s NAME WEBHOOKS AGE mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector 4 21m mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector-1-15-3 2 11s mutatingwebhookconfiguration.admissionregistration.k8s.io/pod-identity-webhook 1 38m mutatingwebhookconfiguration.admissionregistration.k8s.io/vpc-resource-mutating-webhook 1 37m 手順4. データプレーンを更新する アプリケーションのPodの サイドカー コンテナとして起動しているEnvoy(以降では istio-proxy と呼びます)を更新します。 今回は automatic sidecar injection を有効にしているため、 istio-proxy はPod作成時に サイドカー コンテナとして自動的に追加されます。 この場合、 automatic sidecar injection が有効なnamespaceについて、namespaceのlabelを更新した後、Podを再作成することで、 istio-proxy を更新できます。 今回対象となるnamespaceは以下の2つです。 default namespace istio-ingress namespace まず default namespaceに対して以下の手順を実施します。 現在の istio-proxy の状態を確認します。 $ istioctl -n default proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION details-v1-6bd666858f-wq2fr.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 productpage-v1-7f6655c647-s8gdf.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 ratings-v1-7cc7df5fd5-slqgd.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v1-68c7d56667-nvv5q.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v2-759496d8df-gkgdh.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v3-7545ff7c75-68n7b.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 namespaceのlabelを更新します。 istio.io/rev=<(*1)と同じ文字列> labelを設定します。 istio-injection labelが存在する場合は削除します。 $ kubectl label namespace default istio.io/rev=1-15-3 $ kubectl label namespace default istio-injection- 先程のnamespaceのPodを再作成します。 $ kubectl -n default rollout restart deployment 変更後の istio-proxy の状態を確認します。 特に ISTIOD 列と VERSION 列が新しくなっていることを確認します。 $ istioctl proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION details-v1-5954bb9d8-xn5j6.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 productpage-v1-7f6bff5ddd-ms4b4.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 ratings-v1-6f8d5bf8d4-f2jmm.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v1-785b65d7c4-6j4wq.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v2-86b446779b-9wx5q.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v3-5b5b976fbb-cc2j9.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 次に istio-ingress namespaceに対しても同様の手順を実施します。 default namespaceと同じ内容のため詳細は割愛します。 Canary upgradeではこのようにnamespace単位で徐々にデータプレーンを更新できます。 また、もし問題が発生した場合はnamespaceのlabelをもとに戻してPodを再作成することでデータプレーンの更新を ロールバック できます。 手順5. istio/base の defaultRevision を更新する Application CR istio-base の .spec.source.helm.values の defaultRevision を(*1)と同じ文字列へ更新します。 これによりValidatingWebhookConfiguration istiod-default-validator の宛先が古いバージョンの istiod から新しいバージョンの istiod へ変更されます。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.15.3 helm : releaseName : istio-base values : | defaultRevision : "1-15-3" destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy 手順6. istio/gateway を更新する Application CR istio-ingress の targetRevision を更新します。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-ingress namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : gateway targetRevision : 1.15.3 helm : releaseName : istio-ingress destination : server : https://kubernetes.default.svc namespace : istio-ingress 手順7. 古いバージョンの istio/istiod を削除する 古いバージョンの istio/istiod のApplication CRにfinalizerを設定します。 Application CRの削除と同時に関連するリソースを削除するためです。 9 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod namespace : argocd finalizers : - resources-finalizer.argocd.argoproj.io spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.13.4 helm : releaseName : istiod destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle - /webhooks/2/clientConfig/caBundle - /webhooks/3/clientConfig/caBundle 古いバージョンの istio/istiod のApplication CRを削除します。 istio-system namespaceから古いバージョンの istio/istiod が削除されたことを確認します。 $ kubectl -n istio-system get pods,svc,mutatingwebhookconfigurations NAME READY STATUS RESTARTS AGE pod/istiod-1-15-3-59d4cf9474-gklrs 1/1 Running 0 6m1s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istiod-1-15-3 ClusterIP 172.20.213.98 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 6m1s NAME WEBHOOKS AGE mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector-1-15-3 2 6m1s mutatingwebhookconfiguration.admissionregistration.k8s.io/pod-identity-webhook 1 43m mutatingwebhookconfiguration.admissionregistration.k8s.io/vpc-resource-mutating-webhook 1 43m おわりに この記事ではArgo CDを使ってIstioをバージョンアップする方法を紹介しました。 バージョンアップはシステムをセキュアな状態に維持するために欠かせない作業です。 一方でIstioのバージョンアップは、サービスメッシュ上の全アプリケーションに影響のある作業でもあります。 安全かつ効率的なバージョンアップ方法を確立していきたいですね。 最後までお読みいただき、ありがとうございました。 私たちは同じチームで働いてくれる仲間を探しています。 クラウド アーキテクトの業務に興味がある方のご応募をお待ちしています。 クラウドアーキテクト 執筆: @shibata.takao 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました ) 図は https://istio.io/latest/about/service-mesh/ より引用しました。 ↩ 図は https://argo-cd.readthedocs.io/en/stable/ より引用しました。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/in-place/#upgrade-prerequisites をご参照ください。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/canary/#before-you-upgrade をご参照ください。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/in-place/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/helm/ をご参照ください。 ↩ 詳細は https://helm.sh/docs/chart_best_practices/custom_resource_definitions/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/ をご参照ください。 ↩
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 この記事は 電通国際情報サービス Advent Calendar 2022 の5日目の投稿です。 前日の記事は宮澤さんの「お金をかけずに AWS Certified SysOps Administrator - Associateに合格した話」でした。 さて、この記事ではArgo CDを使ってIstioをバージョンアップする方法を紹介します。 はじめに Istioとは Argo CDとは Istioをどうやってバージョンアップするか Argo CDを使ってIstioをバージョンアップする 想定する環境 環境を構築する Amazon EKS Argo CD Istio サンプルアプリケーション 手順1. Canary upgradeが可能か確認する 手順2. istio/base (CRDを含む)を更新する 手順3. 新しいバージョンの istio/istiod を追加する 手順4. データプレーンを更新する 手順5. istio/base の defaultRevision を更新する 手順6. istio/gateway を更新する 手順7. 古いバージョンの istio/istiod を削除する おわりに はじめに Istioとは Istio はサービスメッシュを実現する OSS の1つです。 サービスメッシュとは、マイクロサービスにおける トラフィック 管理、可観測性、セキュリティなどを、インフラスト ラク チャレイヤーで透過的に実現する仕組みです。 サービスメッシュがどんな課題を解決するものかは、以下のスライドが参考になります。 サービスメッシュは本当に必要なのか、何を解決するのか / Service meshes - Do we really need them? What problems do they solve? - Speaker Deck Istioはデータプレーンとコン トロール プレーンの2つの コンポーネント から構成されます。 データプレーン : 実際にサービス間通信を行う部分です。 アプリケーションのプロキシとして Envoy を起動します ( サイドカー コンテナとして起動するケースが多いです)。 Envoyは トラフィック 管理、可観測性、セキュリティなどの機能を提供します。 コン トロール プレーン : Envoyの設定を管理・更新します。 1 Istioのサポートポリシーは Supported Releases に定義されています。 概要は以下のとおりです。 4半期に1回、マイナーリリースを行う。 各マイナーリリースのサポート(=セキュリティパッチの提供など)はN+2マイナーリリースの6週間後まで提供される。 例: v1.11 のサポートは v1.13.0 をリリースした6週間後まで提供される。 Argo CDとは Argo CD は Kubernetes 用の継続的デリバリーツールの1つです。 Argo CDは、git リポジトリ で管理された Kubernetes の マニフェスト の変更を、 Kubernetes クラスタ へデプロイします。 2 Argo CDは GitOps という手法に基づいています。 GitOpsはWeaveworks社が提唱した継続的デリバリー・継続的デプロイメントの方法であり、以下の特徴があります。 システム全体が宣言的に記述されていること。 マニフェスト がgitで管理され、それが信頼できる唯一の情報源(Single Source of Truth)であること。 承認された マニフェスト の変更が自動的にデプロイされること。またその際 Kubernetes クラスタ への認証情報を外部(例えばCIサーバなど)に持たせる必要がないこと。 マニフェスト と Kubernetes クラスタ の間に差分がある場合、それを検知したり、自動的に修正したりできること。 Istioをどうやってバージョンアップするか 先述した通りIstioの各マイナーリリースはリリースから約6〜8ヶ月後にサポートが終了します。 そのため定期的にIstioをバージョンアップする必要があります。 公式ドキュメント には以下の3つのバージョンアップ方法が紹介されています。 istioctl を使ってIn-place upgradeする方法 istioctl を使ってCanary upgradeする方法 Helmを使ってCanary upgradeする方法(α版) なお、In-place upgradeの場合、バージョンアップ前後のバージョン差異は1マイナーリリース以内である必要があります。 3 また、Canary upgradeの場合、バージョンアップ前後のバージョン差異は2マイナーリリース以内であることが推奨されています。 4 実案件の多くは、 Kubernetes マニフェスト のデプロイを、Argo CDなどの継続的デリバリーツールを使って行います。 しかし、上述のドキュメントでは、 Kubernetes クラスタ 管理者が手元からコマンドを実行してバージョンアップする方法しか紹介されていません。 そこで本記事では、Argo CDを使ってIstioをバージョンアップする方法を紹介します。 今回は以下の理由から方法3をベースとします。 公式ドキュメントではIn-place upgradeよりもCanary upgradeを推奨しているため。 5 Argo CDをはじめ 6 、多くの継続的デリバリーツールがHelmをサポートしているため。 Argo CDを使ってIstioをバージョンアップする 想定する環境 本記事では以下の環境を前提とします。 Amazon EKS v1.23 Argo CD v2.4.14 Istio v1.13.4 → v1.15.3 またサンプルアプリケーションとしてIstio公式のサンプルアプリケーションである Bookinfoアプリケーション がデプロイされているものとします。 環境を構築する Amazon EKS Amazon EKS クラスタ を構築します。 本記事では詳細な手順は割愛します。 詳しくは Amazon EKS クラスターの作成 - Amazon EKS をご参照ください。 Argo CD Kubernetes クラスタ へArgo CDをデプロイします。 本記事では詳細な手順は割愛します。 詳しくは以下の公式ドキュメントをご参照ください。 Getting Started - Argo CD - Declarative GitOps CD for Kubernetes Installation - Argo CD - Declarative GitOps CD for Kubernetes Istio IstioのHelm chartは以下の3つを使用します。 istio/base istio/istiod istio/gateway 本記事では以下の手順で Kubernetes クラスタ にIstio v1.13.4がインストールされているものとします。 必要なnamespaceを作成します。 $ kubectl create namespace istio-system $ kubectl create namespace istio-ingress istio-ingress namespaceに automatic sidecar injection を有効にするためのlabelを設定します。 $ kubectl label namespace istio-ingress istio-injection=enabled 以下のArgo CDのApplication CRを kubectl apply コマンドでデプロイします。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.13.4 helm : releaseName : istio-base destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy --- apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.13.4 helm : releaseName : istiod destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle - /webhooks/2/clientConfig/caBundle - /webhooks/3/clientConfig/caBundle --- apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-ingress namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : gateway targetRevision : 1.13.4 helm : releaseName : istio-ingress destination : server : https://kubernetes.default.svc namespace : istio-ingress 上述のApplication CRについてArgo CDのWeb UIからsyncを実行してIstioをデプロイします。 syncは istio-base → istiod → istio-ingress の順に実行します。 本記事ではArgo CDのWeb UIのアクセス方法および操作方法に関する説明は割愛します。詳細は Argo CDの公式ドキュメント をご参照ください。 サンプルアプリケーション 本記事ではサンプルアプリケーションとして default namespaceにIstio公式のサンプルアプリケーションである Bookinfoアプリケーション がデプロイされているものとします。 default namespaceに automatic sidecar injection を有効にするためのlabelを設定します。 $ kubectl label namespace default istio-injection=enabled Bookinfoアプリケーション をデプロイします。 $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/platform/kube/bookinfo.yaml $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/networking/bookinfo-gateway.yaml $ kubectl apply -f https://raw.githubusercontent.com/istio/istio/1.13.4/samples/bookinfo/networking/destination-rule-all.yaml Gateway bookinfo-gateway の .spec.selector を istio=ingressgateway から istio=ingress へ変更します。 $ kubectl patch gateways.networking.istio.io bookinfo-gateway --type=merge -p '{"spec":{"selector":{"istio":"ingress"}}}' 手順1. Canary upgradeが可能か確認する 以下の条件を満たしているか確認します。 Istioのバージョンアップ前後のバージョン差異が2マイナーリリース以内であること。 例えば、現在のバージョンが 1.13.x の場合、 1.14.x と 1.15.x へバージョンアップできます。 istio/base 、 istio/gateway 、CRDに破壊的変更がないこと。 これらのリソースはバージョンアップ前後で共有されるためです。 istio/istiod がバージョンアップ前後で独立しており同じリソースを共有しないこと。 古いバージョンの istio/istiod を安全に削除するためです。 なお istio/istiod のパラメータ revision にはバージョンアップ前後で異なる値を設定します。 kubectl rollout restart コマンドでアプリケーションのPodを安全に再作成できること。 PodがGraceful Shutdownするよう実装・設定されているか。 PodのUpdate strategyが適切に設定されているか。 以下の手順でバージョンアップ前後の互換性が確認できていること。 更新後のバージョンの istioctl を GitHubのリリース からダウンロードする。 istioctl x precheck コマンドを実行して互換性を確認する。 $ istioctl x precheck ✔ No issues found when checking the cluster. Istio is safe to install or upgrade! To get started, check out <https://istio.io/latest/docs/setup/getting-started/> 手順2. istio/base (CRDを含む)を更新する Application CR istio-base の targetRevision を更新します。これによりCRDなどが更新されます。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.15.3 helm : releaseName : istio-base destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy 本来、 helm upgrade コマンドや helm uninstall コマンドでhelm chartを更新/削除しても、CRDは更新/削除されません。 7 ただし、Argo CDは helm template で生成した マニフェスト を kubectl apply しているため、Helm chartを更新することで、CRDを更新できます。 手順3. 新しいバージョンの istio/istiod を追加する 新しいバージョンの istio/istiod のApplication CRをデプロイします。 このとき、以下のフィールドは更新後のバージョンにあわせて変更します。 .metadata.name .spec.source.targetRevision .spec.source.helm.values の revision (*1) .spec.ignoreDifferences 8 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod-1-15-3 namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.15.3 helm : releaseName : istiod values : | revision : "1-15-3" destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector-1-15-3 jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle istio-system namespaceに新しいバージョンの istiod が作成されたことを確認します。 $ kubectl -n istio-system get pods,svc,mutatingwebhookconfigurations NAME READY STATUS RESTARTS AGE pod/istiod-1-15-3-59d4cf9474-gklrs 1/1 Running 0 11s pod/istiod-579df55f96-n4tk8 1/1 Running 0 21m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istiod ClusterIP 172.20.91.227 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 21m service/istiod-1-15-3 ClusterIP 172.20.213.98 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 11s NAME WEBHOOKS AGE mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector 4 21m mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector-1-15-3 2 11s mutatingwebhookconfiguration.admissionregistration.k8s.io/pod-identity-webhook 1 38m mutatingwebhookconfiguration.admissionregistration.k8s.io/vpc-resource-mutating-webhook 1 37m 手順4. データプレーンを更新する アプリケーションのPodの サイドカー コンテナとして起動しているEnvoy(以降では istio-proxy と呼びます)を更新します。 今回は automatic sidecar injection を有効にしているため、 istio-proxy はPod作成時に サイドカー コンテナとして自動的に追加されます。 この場合、 automatic sidecar injection が有効なnamespaceについて、namespaceのlabelを更新した後、Podを再作成することで、 istio-proxy を更新できます。 今回対象となるnamespaceは以下の2つです。 default namespace istio-ingress namespace まず default namespaceに対して以下の手順を実施します。 現在の istio-proxy の状態を確認します。 $ istioctl -n default proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION details-v1-6bd666858f-wq2fr.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 productpage-v1-7f6655c647-s8gdf.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 ratings-v1-7cc7df5fd5-slqgd.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v1-68c7d56667-nvv5q.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v2-759496d8df-gkgdh.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 reviews-v3-7545ff7c75-68n7b.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-579df55f96-n4tk8 1.13.4 namespaceのlabelを更新します。 istio.io/rev=<(*1)と同じ文字列> labelを設定します。 istio-injection labelが存在する場合は削除します。 $ kubectl label namespace default istio.io/rev=1-15-3 $ kubectl label namespace default istio-injection- 先程のnamespaceのPodを再作成します。 $ kubectl -n default rollout restart deployment 変更後の istio-proxy の状態を確認します。 特に ISTIOD 列と VERSION 列が新しくなっていることを確認します。 $ istioctl proxy-status NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD VERSION details-v1-5954bb9d8-xn5j6.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 productpage-v1-7f6bff5ddd-ms4b4.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 ratings-v1-6f8d5bf8d4-f2jmm.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v1-785b65d7c4-6j4wq.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v2-86b446779b-9wx5q.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 reviews-v3-5b5b976fbb-cc2j9.default Kubernetes SYNCED SYNCED SYNCED SYNCED NOT SENT istiod-1-15-3-59d4cf9474-gklrs 1.15.3 次に istio-ingress namespaceに対しても同様の手順を実施します。 default namespaceと同じ内容のため詳細は割愛します。 Canary upgradeではこのようにnamespace単位で徐々にデータプレーンを更新できます。 また、もし問題が発生した場合はnamespaceのlabelをもとに戻してPodを再作成することでデータプレーンの更新を ロールバック できます。 手順5. istio/base の defaultRevision を更新する Application CR istio-base の .spec.source.helm.values の defaultRevision を(*1)と同じ文字列へ更新します。 これによりValidatingWebhookConfiguration istiod-default-validator の宛先が古いバージョンの istiod から新しいバージョンの istiod へ変更されます。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-base namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : base targetRevision : 1.15.3 helm : releaseName : istio-base values : | defaultRevision : "1-15-3" destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : ValidatingWebhookConfiguration name : istiod-default-validator jsonPointers : - /webhooks/0/failurePolicy 手順6. istio/gateway を更新する Application CR istio-ingress の targetRevision を更新します。 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istio-ingress namespace : argocd spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : gateway targetRevision : 1.15.3 helm : releaseName : istio-ingress destination : server : https://kubernetes.default.svc namespace : istio-ingress 手順7. 古いバージョンの istio/istiod を削除する 古いバージョンの istio/istiod のApplication CRにfinalizerを設定します。 Application CRの削除と同時に関連するリソースを削除するためです。 9 apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : istiod namespace : argocd finalizers : - resources-finalizer.argocd.argoproj.io spec : project : default source : repoURL : https://istio-release.storage.googleapis.com/charts chart : istiod targetRevision : 1.13.4 helm : releaseName : istiod destination : server : https://kubernetes.default.svc namespace : istio-system ignoreDifferences : - group : admissionregistration.k8s.io kind : MutatingWebhookConfiguration name : istio-sidecar-injector jsonPointers : - /webhooks/0/clientConfig/caBundle - /webhooks/1/clientConfig/caBundle - /webhooks/2/clientConfig/caBundle - /webhooks/3/clientConfig/caBundle 古いバージョンの istio/istiod のApplication CRを削除します。 istio-system namespaceから古いバージョンの istio/istiod が削除されたことを確認します。 $ kubectl -n istio-system get pods,svc,mutatingwebhookconfigurations NAME READY STATUS RESTARTS AGE pod/istiod-1-15-3-59d4cf9474-gklrs 1/1 Running 0 6m1s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istiod-1-15-3 ClusterIP 172.20.213.98 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 6m1s NAME WEBHOOKS AGE mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector-1-15-3 2 6m1s mutatingwebhookconfiguration.admissionregistration.k8s.io/pod-identity-webhook 1 43m mutatingwebhookconfiguration.admissionregistration.k8s.io/vpc-resource-mutating-webhook 1 43m おわりに この記事ではArgo CDを使ってIstioをバージョンアップする方法を紹介しました。 バージョンアップはシステムをセキュアな状態に維持するために欠かせない作業です。 一方でIstioのバージョンアップは、サービスメッシュ上の全アプリケーションに影響のある作業でもあります。 安全かつ効率的なバージョンアップ方法を確立していきたいですね。 最後までお読みいただき、ありがとうございました。 私たちは同じチームで働いてくれる仲間を探しています。 クラウド アーキテクトの業務に興味がある方のご応募をお待ちしています。 クラウドアーキテクト 執筆: @shibata.takao 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました ) 図は https://istio.io/latest/about/service-mesh/ より引用しました。 ↩ 図は https://argo-cd.readthedocs.io/en/stable/ より引用しました。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/in-place/#upgrade-prerequisites をご参照ください。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/canary/#before-you-upgrade をご参照ください。 ↩ 詳細は https://istio.io/latest/docs/setup/upgrade/in-place/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/helm/ をご参照ください。 ↩ 詳細は https://helm.sh/docs/chart_best_practices/custom_resource_definitions/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/diffing/ をご参照ください。 ↩ 詳細は https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/ をご参照ください。 ↩
アバター
はいどーもー! 好きな AWS サービスは AWS Snowmobile、コミュニケーションIT事業部の宮澤響です!(部署が変わりました!) 本記事は 電通国際情報サービス Advent Calendar 2022 2日目の記事です! 記念すべき1日目である昨日の記事は、中村年宏さんの「 テックブログ開設1周年! 」でした! 弊社のテックブログが無事に1周年を迎えられたことを祝した記事ですので、ぜひご一読ください! 本記事では、私が先日、私自身のポケットマネーを使用せずに「 AWS Certified SysOps Administrator - Associate」に合格した話について、弊社ならではの学習環境のご紹介を交えてまとめます! AWS Certified SysOps Administrator - Associateとは 受験のきっかけ AWSを利用した業務経験 学習方法 参考書 Udemy Business AWS学習環境 ピアソンVUEの試験ラボサンプル 先人たちの合格体験記 試験の感想 まとめ AWS Certified SysOps Administrator - Associateとは AWS Certified SysOps Administrator - Associate (以下、 SOA )とは、 AWS 認定資格の一つで、 AWS 上でのワークロードのデプロイ、管理、運用に関する経験を証明できる資格です。 名称のとおりAssociateレベルの資格であり、 AWS Certified Solutions Architect - Associate (以下、SAA)や AWS Certified Developer - Associate (以下、 DVA )と同じレベルの位置づけとなっています。 また、2022年10月末時点では、 AWS 認定資格の中で唯一、試験内容に試験ラボ( AWS Management Consoleや AWS CLI を実際に操作する実技試験)が導入されている資格でもあります。 受験のきっかけ 私は既にSAAに合格していたため、次は縦方向に攻めて AWS Certified Solutions Architect - Professional (以下、SAP)合格を目指すか、横方向に攻めて SOA 合格を目指すかの二択を考えていました。 その二択で後者を選択した理由は、現在の部署(異動先の部署)では、設計よりも運用のスキルを求められることが多いと考えたためです。 また、SAPに合格している先輩方は多数いらっしゃいますが、試験ラボが導入されて以降の SOA ( SOA -C02)の合格者は社内でもほとんどいなかったため、敢えて人と違うことをしたかったというのも理由の一つです。 AWS を利用した業務経験 結論から申し上げますと1年未満です。 私は現在新卒入社3年目で、入社してからの半年間は新人研修期間のため、実質的な業務に携わっているのは約2年間です。 その中には AWS サービスを使った業務もありましたが、毎日触っていたというわけではありません。 そのため、 AWS サービスを利用した業務経験としては、正確に数えると1年にも満たないと思います。 学習方法 参考書 弊社では、上長の承認を得られれば、業務に関係する内容の書籍を会社の経費で購入できます。 私は AWS認定資格試験テキスト AWS認定SysOpsアドミニストレーター – アソシエイト を会社の経費で購入し、2週ほど精読しました。 以前SAAの学習をした際にも同シリーズのSAA版の書籍を利用したのですが、そちらと比較すると、SAA版はサービスの分野ごとの章構成(コンピューティングサービス、ストレージサービス、など)となっているのに対し、 SOA 版では試験の分野ごとの章構成(信頼性とビジネス継続性、セキュリティと コンプライアンス 、など)となっていました。 そのため、より試験対策特化という印象であり、0から AWS を学習する方向けというよりは、ある程度はサービスの概要を知っている方向けだと感じました。 Udemy Business 弊社では、時間や場所に囚われることなく幅広い学習ニーズに対応することなどを目的として、Udemy Businessが導入されています。Udemy Businessでは、Udemyの全コンテンツのうち、法人向けに厳選されたコンテンツのみが収録されています。 私は以下のコンテンツを受講しました。 【ベストセラー完全日本語化】AWS 認定ソリューションアーキテクト アソシエイト SAA-C03 対応 2022 最新版 2022年10月末時点では日本語版の SOA 対策用の講義コンテンツが存在しなかったため、代わりに試験範囲の重複度合いが大きいSAAのコンテンツを受講しました。 Ultimate AWS Certified SysOps Administrator Associate 2022 スライドだけダウンロードさせてもらいました。 【SOA-C02版】AWS 認定SysOpsアドミニストレーター アソシエイト模擬試験問題集(全4回分294問) それぞれ2週しました。 少し重箱の隅をつつくような問題も含まれている印象です。 試験ラボ部分は実際に操作するわけではなく、操作手順をまとめた文章の正誤判断となっています。 AWS 学習環境 弊社では、 AWS の学習をしたい社員向けに、学習用の AWS アカウント(通称、 AWS 学習環境)を払い出しています。 基本的に機能制限などはなく、2000円/月を目安として自由に利用できます。 また、払い出された段階で、 AWS CloudTrailによるログ取得や Amazon GuardDutyが有効化されています。 私はこちらを利用して、試験ラボ対策のために以下のような操作を試しました。 Amazon VPC 標準的な VPC (マルチAZ、複数プライベートサブネット、複数パブリックサブネット)の作成 VPC フローログの設定 Amazon S3 バケット のバージョニングの設定 デフォルトの暗号化の設定 イベント通知の設定 ライフサイクルルールの設定 レプリケーション ルールの設定 AWS CloudFormation テンプレートファイルのアップロードによるスタックの更新 Amazon CloudWatch+ Amazon SNS メトリクスフィルターの作成 メトリクスフィルターを基にしたアラームの作成 アラーム状態の変化によって通知を送信する SNS トピックの作成 AWS Lambda+ Amazon EventBridge EventBridgeルールによるLambda関数の呼び出し Amazon EC2 + AWS Auto Scaling+Elastic Load Balancing 上記構成の作成 ピアソンVUEの試験ラボサンプル 2022年10月末時点では、試験をピアソンVUEで申し込むと、試験ラボのサンプルを最大3回まで(何回受けても内容は同じですが)無料で受けられました。 実際の試験よりもボリュームは少ない(大問1つのみ)ものの、画面配置や問題の雰囲気を体感するには最適でした。 そのため、テストセンターの場所などに制約がないのであれば、個人的にはピアソンVUEでの受験をおすすめします。 なお、 協定世界時 の2022年内はピアソンVUEまたはPSIのいずれかで試験を受けることができますが、2023年からはピアソンVUEのみとなるようです。 [参考] 先人たちの合格体験記 試験ラボは情報戦です。 先人たちの合格体験記を巡ると、試験ラボにどのような問題が出題されそうか、おおよその見当がつくと思います。 上述の AWS 学習環境を利用した試験ラボ対策も、先人たちが残してくれた情報を基に、出題されやすそうなサービスに的を絞っていました。 試験の感想 選択問題セクションに関しては、回答に自信をもてない問題が最初の2-3問に連続で出題されたためビビりましたが、落ち着いて残りの問題を解いてみると、セクション全体で8割くらいは正解しているだろうという感触でした。 また、試験ラボには大問1つにつき20分以上(私の場合は大問が2つだったため40分以上)時間を残すことが推奨されていますが、私は選択問題セクションを終える段階で3倍の120分を残せていました。 そのため、精神的にも時間的にも余裕をもって試験ラボに臨めました。 試験ラボに関しても、形式はピアソンVUEのサンプル問題と同じ形式、内容も事前に対策していたような内容で出題されたため、安心して回答できました。 ちなみに、受験料やテストセンターまでの交通費も会社の経費で賄っています。 そのため、学習から受験までの全てを実質無料(ポケットマネー的な意味で)で実施できました。 一つ一つの費用を累計するとそれなりの金額になってしまうため、大感謝です。 まとめ 本記事では、私が先日「 AWS Certified SysOps Administrator - Associate」に合格した話をご紹介しました。 SOA (特に試験ラボ)に関する日本語記事はまだあまり多くないため、少しでもこれから SOA 取得を目指す方の参考になれば幸いです。 電通国際情報サービス Advent Calendar 2022 5日目となる来週月曜日の記事は、柴田崇夫さんの「 Argo CDを使ってIstioをバージョンアップする 」です! タイトルのとおり、 Kubernetes 用の継続的デリバリーツールであるArgo CDを利用して、サービスメッシュを実現する OSS であるIstioをバージョンアップする方法について、分かりやすくまとめられた記事となっています。 お楽しみに! 最後までお読みいただき、本当にありがとうございました! 私たちは同じ事業部で共に働いていただける仲間を募集しています。 現在募集している職種は以下です。 クラウドアーキテクト アプリケーションアーキテクト 電通グループ向け基幹システムプロジェクトマネージャ 戦略的IT プロジェクトマネージャ/ITコンサルタント 会社の経費や時間を上手に利用して、仕事をしながら継続的に学習もしていきたい、そんなみなさまのご応募、お待ちしています! 執筆: @miyazawa.hibiki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
アバター
はいどーもー! 好きな AWS サービスは AWS Snowmobile、コミュニケーションIT事業部の宮澤響です!(部署が変わりました!) 本記事は 電通国際情報サービス Advent Calendar 2022 2日目の記事です! 記念すべき1日目である昨日の記事は、中村年宏さんの「 テックブログ開設1周年! 」でした! 弊社のテックブログが無事に1周年を迎えられたことを祝した記事ですので、ぜひご一読ください! 本記事では、私が先日、私自身のポケットマネーを使用せずに「 AWS Certified SysOps Administrator - Associate」に合格した話について、弊社ならではの学習環境のご紹介を交えてまとめます! AWS Certified SysOps Administrator - Associateとは 受験のきっかけ AWSを利用した業務経験 学習方法 参考書 Udemy Business AWS学習環境 ピアソンVUEの試験ラボサンプル 先人たちの合格体験記 試験の感想 おわりに AWS Certified SysOps Administrator - Associateとは AWS Certified SysOps Administrator - Associate (以下、 SOA )とは、 AWS 認定資格の一つで、 AWS 上でのワークロードのデプロイ、管理、運用に関する経験を証明できる資格です。 名称のとおりAssociateレベルの資格であり、 AWS Certified Solutions Architect - Associate (以下、SAA)や AWS Certified Developer - Associate (以下、 DVA )と同じレベルの位置づけとなっています。 また、2022年10月末時点では、 AWS 認定資格の中で唯一、試験内容に試験ラボ( AWS Management Consoleや AWS CLI を実際に操作する実技試験)が導入されている資格でもあります。 受験のきっかけ 私は既にSAAに合格していたため、次は縦方向に攻めて AWS Certified Solutions Architect - Professional (以下、SAP)合格を目指すか、横方向に攻めて SOA 合格を目指すかの二択を考えていました。 その二択で後者を選択した理由は、現在の部署(異動先の部署)では、設計よりも運用のスキルを求められることが多いと考えたためです。 また、SAPに合格している先輩方は多数いらっしゃいますが、試験ラボが導入されて以降の SOA ( SOA -C02)の合格者は社内でもほとんどいなかったため、敢えて人と違うことをしたかったというのも理由の一つです。 AWS を利用した業務経験 結論から申し上げますと1年未満です。 私は現在新卒入社3年目で、入社してからの半年間は新人研修期間のため、実質的な業務に携わっているのは約2年間です。 その中には AWS サービスを使った業務もありましたが、毎日触っていたというわけではありません。 そのため、 AWS サービスを利用した業務経験としては、正確に数えると1年にも満たないと思います。 学習方法 参考書 弊社では、上長の承認を得られれば、業務に関係する内容の書籍を会社の経費で購入できます。 私は AWS認定資格試験テキスト AWS認定SysOpsアドミニストレーター – アソシエイト を会社の経費で購入し、2週ほど精読しました。 以前SAAの学習をした際にも同シリーズのSAA版の書籍を利用したのですが、そちらと比較すると、SAA版はサービスの分野ごとの章構成(コンピューティングサービス、ストレージサービス、など)となっているのに対し、 SOA 版では試験の分野ごとの章構成(信頼性とビジネス継続性、セキュリティと コンプライアンス 、など)となっていました。 そのため、より試験対策特化という印象であり、0から AWS を学習する方向けというよりは、ある程度はサービスの概要を知っている方向けだと感じました。 Udemy Business 弊社では、時間や場所に囚われることなく幅広い学習ニーズに対応することなどを目的として、Udemy Businessが導入されています。Udemy Businessでは、Udemyの全コンテンツのうち、法人向けに厳選されたコンテンツのみが収録されています。 私は以下のコンテンツを受講しました。 【ベストセラー完全日本語化】AWS 認定ソリューションアーキテクト アソシエイト SAA-C03 対応 2022 最新版 2022年10月末時点では日本語版の SOA 対策用の講義コンテンツが存在しなかったため、代わりに試験範囲の重複度合いが大きいSAAのコンテンツを受講しました。 Ultimate AWS Certified SysOps Administrator Associate 2022 スライドだけダウンロードさせてもらいました。 【SOA-C02版】AWS 認定SysOpsアドミニストレーター アソシエイト模擬試験問題集(全4回分294問) それぞれ2週しました。 少し重箱の隅をつつくような問題も含まれている印象です。 試験ラボ部分は実際に操作するわけではなく、操作手順をまとめた文章の正誤判断となっています。 AWS 学習環境 弊社では、 AWS の学習をしたい社員向けに、学習用の AWS アカウント(通称、 AWS 学習環境)を払い出しています。 基本的に機能制限などはなく、2000円/月を目安として自由に利用できます。 また、払い出された段階で、 AWS CloudTrailによるログ取得や Amazon GuardDutyが有効化されています。 私はこちらを利用して、試験ラボ対策のために以下のような操作を試しました。 Amazon VPC 標準的な VPC (マルチAZ、複数プライベートサブネット、複数パブリックサブネット)の作成 VPC フローログの設定 Amazon S3 バケット のバージョニングの設定 デフォルトの暗号化の設定 イベント通知の設定 ライフサイクルルールの設定 レプリケーション ルールの設定 AWS CloudFormation テンプレートファイルのアップロードによるスタックの更新 Amazon CloudWatch+ Amazon SNS メトリクスフィルターの作成 メトリクスフィルターを基にしたアラームの作成 アラーム状態の変化によって通知を送信する SNS トピックの作成 AWS Lambda+ Amazon EventBridge EventBridgeルールによるLambda関数の呼び出し Amazon EC2 + AWS Auto Scaling+Elastic Load Balancing 上記構成の作成 ピアソンVUEの試験ラボサンプル 2022年10月末時点では、試験をピアソンVUEで申し込むと、試験ラボのサンプルを最大3回まで(何回受けても内容は同じですが)無料で受けられました。 実際の試験よりもボリュームは少ない(大問1つのみ)ものの、画面配置や問題の雰囲気を体感するには最適でした。 そのため、テストセンターの場所などに制約がないのであれば、個人的にはピアソンVUEでの受験をおすすめします。 なお、 協定世界時 の2022年内はピアソンVUEまたはPSIのいずれかで試験を受けることができますが、2023年からはピアソンVUEのみとなるようです。 [参考] 先人たちの合格体験記 試験ラボは情報戦です。 先人たちの合格体験記を巡ると、試験ラボにどのような問題が出題されそうか、おおよその見当がつくと思います。 上述の AWS 学習環境を利用した試験ラボ対策も、先人たちが残してくれた情報を基に、出題されやすそうなサービスに的を絞っていました。 試験の感想 選択問題セクションに関しては、回答に自信をもてない問題が最初の2-3問に連続で出題されたためビビりましたが、落ち着いて残りの問題を解いてみると、セクション全体で8割くらいは正解しているだろうという感触でした。 また、試験ラボには大問1つにつき20分以上(私の場合は大問が2つだったため40分以上)時間を残すことが推奨されていますが、私は選択問題セクションを終える段階で3倍の120分を残せていました。 そのため、精神的にも時間的にも余裕をもって試験ラボに臨めました。 試験ラボに関しても、形式はピアソンVUEのサンプル問題と同じ形式、内容も事前に対策していたような内容で出題されたため、安心して回答できました。 ちなみに、受験料やテストセンターまでの交通費も会社の経費で賄っています。 そのため、学習から受験までの全てを実質無料(ポケットマネー的な意味で)で実施できました。 一つ一つの費用を累計するとそれなりの金額になってしまうため、大感謝です。 おわりに 本記事では、私が先日「 AWS Certified SysOps Administrator - Associate」に合格した話をご紹介しました。 SOA (特に試験ラボ)に関する日本語記事はまだあまり多くないため、少しでもこれから SOA 取得を目指す方の参考になれば幸いです。 電通国際情報サービス Advent Calendar 2022 5日目となる来週月曜日の記事は、柴田崇夫さんの「 Argo CDを使ってIstioをバージョンアップする 」です! タイトルのとおり、 Kubernetes 用の継続的デリバリーツールであるArgo CDを利用して、サービスメッシュを実現する OSS であるIstioをバージョンアップする方法について、分かりやすくまとめられた記事となっています。 お楽しみに! 最後までお読みいただき、本当にありがとうございました! 私たちは同じ事業部で共に働いていただける仲間を募集しています! 会社の経費や時間を上手に利用して、仕事をしながら継続的に学習もしていきたい、そんなみなさまのご応募、お待ちしています! <電通×IT>電通グループ基幹システムプロジェクトマネージャー エンタープライズ向けDX推進リーダー/エンジニア <電通×IT>クラウドアーキテクト <電通×IT>アプリケーションアーキテクト 製品・プラットフォーム開発エンジニア 執筆: @miyazawa.hibiki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
アバター
こんにちは。 X イノベーション 本部ソフトウェアデザインセンターの中村 年宏です。 本記事は 電通国際情報サービス Advent Calendar 2022 の1日目の記事です。 はじめに 企画検討 トライアル実施 編集部設立 正式運営開始 振り返り実施 そして現在 おわりに はじめに 本テックブログですが、本日12月1日をもってめでたく開設1周年を迎えることができました(検証目的で11月に最初の投稿をしましたが 開設を宣言したのが昨年の今日でした )。 がんばり過ぎずに肩の力を抜いて継続するというスタンスで取り組んできましたが、幸いにも多くの方の協力を得ることができ、この1年間で約130の記事を公開できました。アクセス数も伸びており順調に歩みを進められている実感があります。 執筆や運営に協力してくれた社員の方たち、それから本ブログを読んでいただいた皆さまに感謝いたします。 私は運営チームの一員としてテックブログに企画段階から携わってきました。本記事では、テックブログ開設の少し前からこれまでを振り返って運営チームがどんなことをしてきたのか、何を意識してきたかを紹介したいと思います。 企画検討 ISIDがテックブログを始めた理由 にも記載がありますが、立ち上げは社内の ボトムアップ の改善活動から始まりました。部門横断の有志でテックブログの企画を考え、必要な予算を見積り、上層部からトライアル実施の承認を取り付けました。 まだこの時点ではごく少数でしたがコアメンバでコンセプトを定めスピーディーに活動できたのは最初の土台固めに効果的だったと感じています。 トライアル実施 トライアル実施に向けて最初にしたのは、執筆・レビュー・公開のフローの作成でした。特にレビューについては、広報の担当部署にも参画を依頼し コンプライアンス の観点を盛り込むことにしました。 次に検討したのが執筆者の募集です。単に記事を書いてほしいと呼びかけても多くは集まらないと思われたので、 アドベントカレンダー を活用することにしました。 アドベントカレンダー のちょっとしたお祭り気分を利用して社内に周知すれば執筆者が集まりやすいと考えたのです。 加えて事前にテックブログと アドベントカレンダー について説明会を行うことにしました。説明会には、記事の執筆希望者はもちろん執筆に迷っている人も参加歓迎としハードルを下げることを意識しました。 このような工夫もあって多くの方に説明会に参加いただき、 アドベントカレンダー に必要な25名もすぐに集まりました。25名が埋まった2021年版の アドベントカレンダー はこちらです。 adventar.org 全員が担当の期日までに記事を完成させ、 アドベントカレンダー は無事成功しました。 アドベントカレンダー 終了後は執筆者にアンケートを採り、テックブログに意義を感じるかどうか、執筆から公開に至るまでの運営フローに問題はないかの確認をしました。 また、特に重要な確認事項として運営チームへの参加意欲を問う設問をアンケートに盛り込みました。この設問の回答結果に関しては少し不安だったのですが、ふたを開けてみれば10名が「運営チームに参加したい」と回答してくれました。説明会でテックブログの意義に共感してくれたからだと思っています。 編集部設立 運営チームに参加したいと回答してくれた10名を集めて「テックブログ編集部」というバーチャル組織を設立することにしました。編集部の中には、以下の4つのチームを作りました。 デザインチーム マーケティング チーム 執筆管理チーム 統括チーム デザインチームは、ブログのデザインをカッコよく使いやすくすることを目指しました。活動成果は テックブログのデザイン刷新(実装編) をご覧ください。 マーケティング チームは、テックブログをより多くの方に届けるための施策を検討します。まずはどれだけ読まれているのかの把握が重要だと考え、 アクセス解析 を中心に活動することにしました。 執筆管理チームは、記事が定期的に公開され続けるための活動をします。レビューサービスのアカウント発行、 ガイドライン の整備、記事のストックが足りているかの確認、レビュアーの割り当て、などです。 統括チームは、編集部の運営をします。隔週で定例会を開き、各チームの活動状況や課題を確認し、必要に応じて人事や広報を担当する部署や上層部との渉外も担当します。それから、社内に向けた周知活動も行います。私はこのチームに入りました。 編集部の運営を始めておおよそ10カ月ほど経ちますが、テックブログを安定的に継続する上でこのような組織的な活動が大きな役割を担ってきたと感じています。 正式運営開始 編集部設立後2カ月ほどテックブログを運営し特段問題がないことを確認できたので、正式運営開始へ向けた調整に入ることにしました。具体的には上層部への説明です。 上層部へは、トライアル実施の結果、編集部の体制、今後の活動計画などを報告し無事承認を得られました。承認を得た後は全社周知メールの準備をしたり、コーポレートサイトに掲載すべく広報担当部署と調整したりしました。 広報担当部署と連携してコーポレートサイトに載せたお知らせはこちらになります。4月4日のことでした。 www.isid.co.jp もともと ボトムアップ で始めた活動であり自由度は高いのですが、節目の度に上層部へ説明し理解を得ることは重要視しています。上層部が承認した公式な活動とすることで多くの関係者とより円滑に連携しやすくなると考えています。 振り返り実施 トライアル期間含めて約6カ月が経った頃、執筆者にアンケートを採り振り返りを行いました。アンケートの主な設問は次のようなものでした。 自身の成長にポジティブな効果があると感じるか? 社内のコミュニケーション円滑化にポジティブな効果があると感じるか? 知り合いの社員に執筆を奨めたいと思うか? 幸い、いずれの設問に対してもポジティブな回答が多く、当初の狙いが達成できていることを確認できました。編集部の自信にもつながったと感じます。このアンケート結果は他の 定量 データ(6カ月間に公開した記事数や月間のページビュー数など)と一緒に社内の情報共有サイトに掲載しました。 情報を共有するのは、テックブログに関して社員の理解を得るためです。上層部の理解を得ることを重要視していることは上述した通りですが、それと同等以上に社員からの理解も重要だと考えています。 今後もこのような振り返り結果の共有は半年毎に実施していく予定です。 そして現在 4月に正式運営を開始してからも現在まで大きな問題なく運営を続けられています。 そして、今年も アドベントカレンダー を実施することになりました。 アドベントカレンダー の執筆者は募集を開始したところわずか3日ほどで募集人数に達し、ありがたいことに執筆者が自発的に集まる状況になっています。 今年の アドベントカレンダー はこちらになります。 adventar.org カレンダーを見ていただくとお分かりだと思いますが、土日は「ISID テックブログ」と記載されており個人名の登録になっていません。これは、土日については記事を公開しないことを表しています。 アドベントカレンダー と言えば12月1日から25日まで途切れることなく毎日記事を書くのが一般的だとは思いますが、私たちは土日の執筆および公開を取りやめることにしました。テックブログの活動は業務の一部であり、たとえ予約投稿の機能を使って実際には働かないにしても、休暇時に取り組む必要性はないと考えたからです。したがって、今回は土日を除いた計17日間で アドベントカレンダー を進めていきます。 おわりに テックブログ開設1周年ということで、テックブログの企画段階から現在までの取り組みを紹介いたしました。簡単にまとめると以下の点が重要だったと感じています。 土台を固めるために最初は数人のコアメンバで始めた テックブログの意義に賛同してもらうために説明会を行った 安定的に運営するために編集部を作った 上層部の理解を得るために節目ごとに報告を行った 社員の理解を得るために振り返り結果を共有した 会社の文化や規模などによって最適な取り組みは異なると思いますが、これからテックブログを立ち上げたいと思われている方たちの参考になれば幸いです。 さて、私が所属するソフトウェアデザインセンターでは、以下の職種で採用を行っております。 ソリューションアーキテクト セキュリティエンジニア(セキュリティ設計) ソフトウェアデザインセンターのメンバは本ブログに記事を多数掲載しています。ご興味があれば 「ソフトウェアデザインセンター」の検索でヒットする記事 もご覧ください。 まずはカジュアルに話をしてみたいという方は、下記のリンク先ページの「面談に申し込む」からエントリをよろしくお願いします。 techplay.jp 執筆: @nakamura.toshihiro 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは。 X イノベーション 本部ソフトウェアデザインセンターの中村 年宏です。 本記事は 電通国際情報サービス Advent Calendar 2022 の1日目の記事です。 はじめに 企画検討 トライアル実施 編集部設立 正式運営開始 振り返り実施 そして現在 おわりに はじめに 本テックブログですが、本日12月1日をもってめでたく開設1周年を迎えることができました(検証目的で11月に最初の投稿をしましたが 開設を宣言したのが昨年の今日でした )。 がんばり過ぎずに肩の力を抜いて継続するというスタンスで取り組んできましたが、幸いにも多くの方の協力を得ることができ、この1年間で約130の記事を公開できました。アクセス数も伸びており順調に歩みを進められている実感があります。 執筆や運営に協力してくれた社員の方たち、それから本ブログを読んでいただいた皆さまに感謝いたします。 私は運営チームの一員としてテックブログに企画段階から携わってきました。本記事では、テックブログ開設の少し前からこれまでを振り返って運営チームがどんなことをしてきたのか、何を意識してきたかを紹介したいと思います。 企画検討 ISIDがテックブログを始めた理由 にも記載がありますが、立ち上げは社内の ボトムアップ の改善活動から始まりました。部門横断の有志でテックブログの企画を考え、必要な予算を見積り、上層部からトライアル実施の承認を取り付けました。 まだこの時点ではごく少数でしたがコアメンバでコンセプトを定めスピーディーに活動できたのは最初の土台固めに効果的だったと感じています。 トライアル実施 トライアル実施に向けて最初にしたのは、執筆・レビュー・公開のフローの作成でした。特にレビューについては、広報の担当部署にも参画を依頼し コンプライアンス の観点を盛り込むことにしました。 次に検討したのが執筆者の募集です。単に記事を書いてほしいと呼びかけても多くは集まらないと思われたので、 アドベントカレンダー を活用することにしました。 アドベントカレンダー のちょっとしたお祭り気分を利用して社内に周知すれば執筆者が集まりやすいと考えたのです。 加えて事前にテックブログと アドベントカレンダー について説明会を行うことにしました。説明会には、記事の執筆希望者はもちろん執筆に迷っている人も参加歓迎としハードルを下げることを意識しました。 このような工夫もあって多くの方に説明会に参加いただき、 アドベントカレンダー に必要な25名もすぐに集まりました。25名が埋まった2021年版の アドベントカレンダー はこちらです。 adventar.org 全員が担当の期日までに記事を完成させ、 アドベントカレンダー は無事成功しました。 アドベントカレンダー 終了後は執筆者にアンケートを採り、テックブログに意義を感じるかどうか、執筆から公開に至るまでの運営フローに問題はないかの確認をしました。 また、特に重要な確認事項として運営チームへの参加意欲を問う設問をアンケートに盛り込みました。この設問の回答結果に関しては少し不安だったのですが、ふたを開けてみれば10名が「運営チームに参加したい」と回答してくれました。説明会でテックブログの意義に共感してくれたからだと思っています。 編集部設立 運営チームに参加したいと回答してくれた10名を集めて「テックブログ編集部」というバーチャル組織を設立することにしました。編集部の中には、以下の4つのチームを作りました。 デザインチーム マーケティング チーム 執筆管理チーム 統括チーム デザインチームは、ブログのデザインをカッコよく使いやすくすることを目指しました。活動成果は テックブログのデザイン刷新(実装編) をご覧ください。 マーケティング チームは、テックブログをより多くの方に届けるための施策を検討します。まずはどれだけ読まれているのかの把握が重要だと考え、 アクセス解析 を中心に活動することにしました。 執筆管理チームは、記事が定期的に公開され続けるための活動をします。レビューサービスのアカウント発行、 ガイドライン の整備、記事のストックが足りているかの確認、レビュアーの割り当て、などです。 統括チームは、編集部の運営をします。隔週で定例会を開き、各チームの活動状況や課題を確認し、必要に応じて人事や広報を担当する部署や上層部との渉外も担当します。それから、社内に向けた周知活動も行います。私はこのチームに入りました。 編集部の運営を始めておおよそ10カ月ほど経ちますが、テックブログを安定的に継続する上でこのような組織的な活動が大きな役割を担ってきたと感じています。 正式運営開始 編集部設立後2カ月ほどテックブログを運営し特段問題がないことを確認できたので、正式運営開始へ向けた調整に入ることにしました。具体的には上層部への説明です。 上層部へは、トライアル実施の結果、編集部の体制、今後の活動計画などを報告し無事承認を得られました。承認を得た後は全社周知メールの準備をしたり、コーポレートサイトに掲載すべく広報担当部署と調整したりしました。 広報担当部署と連携してコーポレートサイトに載せたお知らせはこちらになります。4月4日のことでした。 www.isid.co.jp もともと ボトムアップ で始めた活動であり自由度は高いのですが、節目の度に上層部へ説明し理解を得ることは重要視しています。上層部が承認した公式な活動とすることで多くの関係者とより円滑に連携しやすくなると考えています。 振り返り実施 トライアル期間含めて約6カ月が経った頃、執筆者にアンケートを採り振り返りを行いました。アンケートの主な設問は次のようなものでした。 自身の成長にポジティブな効果があると感じるか? 社内のコミュニケーション円滑化にポジティブな効果があると感じるか? 知り合いの社員に執筆を奨めたいと思うか? 幸い、いずれの設問に対してもポジティブな回答が多く、当初の狙いが達成できていることを確認できました。編集部の自信にもつながったと感じます。このアンケート結果は他の 定量 データ(6カ月間に公開した記事数や月間のページビュー数など)と一緒に社内の情報共有サイトに掲載しました。 情報を共有するのは、テックブログに関して社員の理解を得るためです。上層部の理解を得ることを重要視していることは上述した通りですが、それと同等以上に社員からの理解も重要だと考えています。 今後もこのような振り返り結果の共有は半年毎に実施していく予定です。 そして現在 4月に正式運営を開始してからも現在まで大きな問題なく運営を続けられています。 そして、今年も アドベントカレンダー を実施することになりました。 アドベントカレンダー の執筆者は募集を開始したところわずか3日ほどで募集人数に達し、ありがたいことに執筆者が自発的に集まる状況になっています。 今年の アドベントカレンダー はこちらになります。 adventar.org カレンダーを見ていただくとお分かりだと思いますが、土日は「ISID テックブログ」と記載されており個人名の登録になっていません。これは、土日については記事を公開しないことを表しています。 アドベントカレンダー と言えば12月1日から25日まで途切れることなく毎日記事を書くのが一般的だとは思いますが、私たちは土日の執筆および公開を取りやめることにしました。テックブログの活動は業務の一部であり、たとえ予約投稿の機能を使って実際には働かないにしても、休暇時に取り組む必要性はないと考えたからです。したがって、今回は土日を除いた計17日間で アドベントカレンダー を進めていきます。 おわりに テックブログ開設1周年ということで、テックブログの企画段階から現在までの取り組みを紹介いたしました。簡単にまとめると以下の点が重要だったと感じています。 土台を固めるために最初は数人のコアメンバで始めた テックブログの意義に賛同してもらうために説明会を行った 安定的に運営するために編集部を作った 上層部の理解を得るために節目ごとに報告を行った 社員の理解を得るために振り返り結果を共有した 会社の文化や規模などによって最適な取り組みは異なると思いますが、これからテックブログを立ち上げたいと思われている方たちの参考になれば幸いです。 さて、私が所属するソフトウェアデザインセンターでは、以下の職種で採用を行っております。 ソリューションアーキテクト セキュリティエンジニア(セキュリティ設計) ソフトウェアデザインセンターのメンバは本ブログに記事を多数掲載しています。ご興味があれば 「ソフトウェアデザインセンター」の検索でヒットする記事 もご覧ください。 まずはカジュアルに話をしてみたいという方は、下記のリンク先ページの「面談に申し込む」からエントリをよろしくお願いします。 techplay.jp 執筆: @nakamura.toshihiro 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは。ISID 金融ソリューション事業部の若本です。 最近、写真から3Dモデルを生成する技術の話題を目にすることが多くなってきました。そこで、写真から3Dを生成できるAIモデルであるInstant NeRFを使って、 スマートフォン の撮影画像からどのような結果が出るのか試してみます。複数の撮影条件を用意し、どのように撮影すればうまく レンダリング されるかを検証したいと思います。 Instant NeRF とは? Instant NeRF 1 はNeRF 2 を軽量化したAIモデルです。Instant NeRFを用いることで、10秒足らずで数十枚の画像から3Dシーン(volume)を レンダリング できます。下記は公式が出しているInstant NeRFの動画です。 NeRFモデルではカメラの座標や光線方向(見ている方向)の情報をもとに、各 ピクセル の色や透明度を学習します。カメラの座標や方向は画像からだとわからないため、これらの推定は別の アルゴリズム であるCOLMAP 3 で行いました。NeRFそのものの詳細な構造やその仕組みについては、日本語の解説記事も多く出ているのでここでは触れないこととします。 NeRF でよくある失敗 Instant NeRFをいざ使ってみてよくある(と個人的に思っている)失敗は、「想像していたよりもきれいに出力されなかった」というものです。 このとき、NeRFの主な失敗例は下記の2つです。※下記名称は一般的なものではありません floaters 雲や霧のようなぼやけたオブジェクトが、被写体周辺に発生します。 ghost 見る角度を変えた際に、被写体が複数現れます。 上記2点は、いずれもデータの整合性が解決できなかったことが原因です。 その他にも、画像系のAIモデルは入力に対して非常に敏感なため、人間の目には判別できないようなわずかな違いでも出力結果が大きく変わってしまうことがあります。 とはいえ、うまく撮影やチューニングできれば高い性能が期待できるNeRFを、早々に諦めてしまうのは勿体ない!ということで、AIモデルにとって重要な入力の観点から、どのように撮影した画像であればNeRFの出力が良くなるのかを実験してみます。 前提 主要な前提は下記の通りです。 iphone 11の外向きカメラでビデオ撮影 解像度:1920 × 1080 FPS : 30 撮影時間:~10秒程度 instant NeRFの実行環境: NVIDIA Instant NeRF NGP 4 GPU : NVIDIA GeForce RTX 3060 Instant- NGP の環境構築は実施済みであることを想定しています。環境構築には日本語の解説記事も多く出ているので、そちらをご参照ください。 様々な条件で実験 NeRFを綺麗に生成するTipsについては NVIDIA Instant NeRF NGPの公式 にもまとまっています。リンク先では、主に①画像の質、②画像の量について述べられています。 今回はそれらを踏襲しつつ、撮影条件として精度に影響しそうなものをいくつか実験しました。 1. 撮影方法の違い まずは、撮影方法について見てみます。試したのは、1) 被写体に沿って動かす方法(平行移動と呼称します)と、2) カメラ位置を固定して視線だけ変える方法(視点回転と呼称します)、の2つです。 それぞれの方法で撮影した結果を見てみます。 視点回転で撮影した場合、撮影時に被写体はちゃんと映っていますが、まったく出力できていません。一方、平行移動で撮影した場合には、volumeがきれいに出力されていることがわかります。 これは、COLMAPがカメラの位置を正しく推定できていないことが原因です。 2. 撮影スピードの違い 次は、撮影スピードの違いです。素早く動きながら撮った(=モーションブラーの多い)写真がアウトプットに悪影響を及ぼすことは想像に難くないですが、どのような出力が出るのか試してみます。 予想通り、撮影スピードが早い写真では、出力の質がよくありません。ブレた画像をvolume レンダリング した結果、椅子の前に大量のfloaterが形成されてしまっています。これは別の角度から見た画像が、ブレた画像と一致するように学習してしまった結果、ブレを表現するためにfloaterが飛び出してしまったようです。 3. 撮影距離の違い さらに、撮影距離についても検証しました。2.の検証時にはオブジェクトと近い状態で撮影しています、加えていくつかの条件で撮影を実施しました。 ここで、示唆は以下の2つです。 ①:目印となるもの(被写体、背景)が遠い場合はカメラの位置推定がずれてしまい、出力の質が低くなる。 ②:撮影距離を動的に変更すると、カメラの位置推定ミスや焦点のぼやけなどが発生し、出力の質が低くなる。 遠ければ遠いほど、カメラの位置推定の誤差が大きく影響してしまうようです。近めと遠めの画像が混在していると、そちらも悪影響を及ぼす可能性があります。 4. 撮影角度の違い 撮影する角度の多さによる変化についても見てみましょう。つまり、「見えていない角度の写真も取り込むべきか」を検証します。 実験結果から、一長一短であるといえます。 メリットはオブジェクトが明瞭になることです。1方向の写真からでは、見えていない下側はぼんやりと再現されていましたが、角度を増やすにつれて下側のfloaterが減っています。また、オブジェクトに空いている穴も撮影角度を増やすことで減っていました。 一方、デメリットは上側のfloaterの増加です。横や下から撮った画像を増やすことで、出力上部に雲のようなものが増えました。これは撮影時の「白飛び」によるもので、日光がカメラに入ってしまったことが原因です。 角度を増やすほどオブジェクトの正確さは増しますが、 光源が映りこむケースに気を付ける必要 がありそうです。今回は撮影した動画をそのままInstant NeRFで読み込む形としましたが、写真の「白飛び」や「黒潰れ」などを抑える前処理は必須かもしれません。 5. 撮影機器の違い 最後に、レンズの違いについて見てみます。 iphone 11以降は超広角カメラが搭載されているため、カメラモードを切り替えて撮影した場合にどのような結果が得られるのか検証します。ちなみに、ここまでの結果はすべて広角レンズで撮影したものです。 同じような入力映像にもかかわらず、驚くほど出力のクオリティが上がりました!広角では平べったく出力されている椅子が、超広角では立体的に出力できています。 レンズがより広角であることは、かなり重要な要素のようです。具体的には、 被写界深度 が深いとより良い結果となることが考えられます。1. の結果より、広角レンズでも近めで撮影した場合に良い結果が得られているため、被写体から背景まで適切にピントが合った状態で撮影できていることが重要と推察されます。 6. その他気づき 画像枚数について 20~30枚程度のバラバラに撮った画像ではまったく復元できませんでした。50枚以上あれば見えやすくなることは確認できました。 画像枚数が増えても、Instant NeRFの計算時間の変化は体感的にありませんでした。ただし、COLMAPの計算時間は画像量に比例して伸びていきそうです。筆者の環境では、300枚の画像からCOLMAPでカメラ位置を計算するのに1時間ほど要しました。 結論 撮影機材が重要 です!! 目印となるもの(被写体や背景)を視界に入れつつ、 カメラがきれいに撮れる範囲でゆっくり動き回りましょう 。 よりクオリティを追求するなら角度を網羅するほうがベターです。 今回検証できなかったこと 本記事では主に動画の撮影方法からNeRFの出力を改善する方法を模索しました。今回検証した項目以外にも、下記のような項目を見直すことで性能の改善が期待できます。 Instant NGP 内の GUI でパラメータチューニング NGP 内ではモデルのパラメータや レンダリング の アルゴリズム などを設定することが可能です。今回はすべてデフォルトとしましたが、より鮮明に表示するためには GUI 上での設定も必要になります。 カメラ位置推定のチューニング floaterやghostの原因となっている画像を見つけて、直接 json から削除が可能です。 結局何枚必要なのか? Instant NGP のTipsには50~150枚が適正との表記がありましたが、多すぎた場合のデメリットはあまり感じられませんでした。被写体によって適正枚数が異なる可能性もあります。 まとめ 今回は、NeRFモデルの入力とする画像を変更することによってどのように出力が変わるのかについて検証を行いました。些細に思える違いでもかなり出力結果は変わってしまい、データの収集や処理にコツが求められると感じました。 NeRFモデル自身は非常に強力なので、 ユースケース やうまく レンダリング できる条件を見極めて使いこなしたいと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ 執筆: @wakamoto.ryosuke 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました ) Instant Neural Graphics Primitives with a Multiresolution Hash Encoding( https://arxiv.org/pdf/2201.05989.pdf ) ↩ NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis( https://arxiv.org/pdf/2003.08934.pdf ) ↩ colmap( https://github.com/colmap/colmap ) ↩ NVIDIA Instant NeRF NGP ( https://github.com/NVlabs/instant-ngp ) ↩
アバター
こんにちは。ISID 金融ソリューション事業部の若本です。 最近、写真から3Dモデルを生成する技術の話題を目にすることが多くなってきました。そこで、写真から3Dを生成できるAIモデルであるInstant NeRFを使って、 スマートフォン の撮影画像からどのような結果が出るのか試してみます。複数の撮影条件を用意し、どのように撮影すればうまく レンダリング されるかを検証したいと思います。 Instant NeRF とは? Instant NeRF 1 はNeRF 2 を軽量化したAIモデルです。Instant NeRFを用いることで、10秒足らずで数十枚の画像から3Dシーン(volume)を レンダリング できます。下記は公式が出しているInstant NeRFの動画です。 NeRFモデルではカメラの座標や光線方向(見ている方向)の情報をもとに、各 ピクセル の色や透明度を学習します。カメラの座標や方向は画像からだとわからないため、これらの推定は別の アルゴリズム であるCOLMAP 3 で行いました。NeRFそのものの詳細な構造やその仕組みについては、日本語の解説記事も多く出ているのでここでは触れないこととします。 NeRF でよくある失敗 Instant NeRFをいざ使ってみてよくある(と個人的に思っている)失敗は、「想像していたよりもきれいに出力されなかった」というものです。 このとき、NeRFの主な失敗例は下記の2つです。※下記名称は一般的なものではありません floaters 雲や霧のようなぼやけたオブジェクトが、被写体周辺に発生します。 ghost 見る角度を変えた際に、被写体が複数現れます。 上記2点は、いずれもデータの整合性が解決できなかったことが原因です。 その他にも、画像系のAIモデルは入力に対して非常に敏感なため、人間の目には判別できないようなわずかな違いでも出力結果が大きく変わってしまうことがあります。 とはいえ、うまく撮影やチューニングできれば高い性能が期待できるNeRFを、早々に諦めてしまうのは勿体ない!ということで、AIモデルにとって重要な入力の観点から、どのように撮影した画像であればNeRFの出力が良くなるのかを実験してみます。 前提 主要な前提は下記の通りです。 iphone 11の外向きカメラでビデオ撮影 解像度:1920 × 1080 FPS : 30 撮影時間:~10秒程度 instant NeRFの実行環境: NVIDIA Instant NeRF NGP 4 GPU : NVIDIA GeForce RTX 3060 Instant- NGP の環境構築は実施済みであることを想定しています。環境構築には日本語の解説記事も多く出ているので、そちらをご参照ください。 様々な条件で実験 NeRFを綺麗に生成するTipsについては NVIDIA Instant NeRF NGPの公式 にもまとまっています。リンク先では、主に①画像の質、②画像の量について述べられています。 今回はそれらを踏襲しつつ、撮影条件として精度に影響しそうなものをいくつか実験しました。 1. 撮影方法の違い まずは、撮影方法について見てみます。試したのは、1) 被写体に沿って動かす方法(平行移動と呼称します)と、2) カメラ位置を固定して視線だけ変える方法(視点回転と呼称します)、の2つです。 それぞれの方法で撮影した結果を見てみます。 視点回転で撮影した場合、撮影時に被写体はちゃんと映っていますが、まったく出力できていません。一方、平行移動で撮影した場合には、volumeがきれいに出力されていることがわかります。 これは、COLMAPがカメラの位置を正しく推定できていないことが原因です。 2. 撮影スピードの違い 次は、撮影スピードの違いです。素早く動きながら撮った(=モーションブラーの多い)写真がアウトプットに悪影響を及ぼすことは想像に難くないですが、どのような出力が出るのか試してみます。 予想通り、撮影スピードが早い写真では、出力の質がよくありません。ブレた画像をvolume レンダリング した結果、椅子の前に大量のfloaterが形成されてしまっています。これは別の角度から見た画像が、ブレた画像と一致するように学習してしまった結果、ブレを表現するためにfloaterが飛び出してしまったようです。 3. 撮影距離の違い さらに、撮影距離についても検証しました。2.の検証時にはオブジェクトと近い状態で撮影しています、加えていくつかの条件で撮影を実施しました。 ここで、示唆は以下の2つです。 ①:目印となるもの(被写体、背景)が遠い場合はカメラの位置推定がずれてしまい、出力の質が低くなる。 ②:撮影距離を動的に変更すると、カメラの位置推定ミスや焦点のぼやけなどが発生し、出力の質が低くなる。 遠ければ遠いほど、カメラの位置推定の誤差が大きく影響してしまうようです。近めと遠めの画像が混在していると、そちらも悪影響を及ぼす可能性があります。 4. 撮影角度の違い 撮影する角度の多さによる変化についても見てみましょう。つまり、「見えていない角度の写真も取り込むべきか」を検証します。 実験結果から、一長一短であるといえます。 メリットはオブジェクトが明瞭になることです。1方向の写真からでは、見えていない下側はぼんやりと再現されていましたが、角度を増やすにつれて下側のfloaterが減っています。また、オブジェクトに空いている穴も撮影角度を増やすことで減っていました。 一方、デメリットは上側のfloaterの増加です。横や下から撮った画像を増やすことで、出力上部に雲のようなものが増えました。これは撮影時の「白飛び」によるもので、日光がカメラに入ってしまったことが原因です。 角度を増やすほどオブジェクトの正確さは増しますが、 光源が映りこむケースに気を付ける必要 がありそうです。今回は撮影した動画をそのままInstant NeRFで読み込む形としましたが、写真の「白飛び」や「黒潰れ」などを抑える前処理は必須かもしれません。 5. 撮影機器の違い 最後に、レンズの違いについて見てみます。 iphone 11以降は超広角カメラが搭載されているため、カメラモードを切り替えて撮影した場合にどのような結果が得られるのか検証します。ちなみに、ここまでの結果はすべて広角レンズで撮影したものです。 同じような入力映像にもかかわらず、驚くほど出力のクオリティが上がりました!広角では平べったく出力されている椅子が、超広角では立体的に出力できています。 レンズがより広角であることは、かなり重要な要素のようです。具体的には、 被写界深度 が深いとより良い結果となることが考えられます。1. の結果より、広角レンズでも近めで撮影した場合に良い結果が得られているため、被写体から背景まで適切にピントが合った状態で撮影できていることが重要と推察されます。 6. その他気づき 画像枚数について 20~30枚程度のバラバラに撮った画像ではまったく復元できませんでした。50枚以上あれば見えやすくなることは確認できました。 画像枚数が増えても、Instant NeRFの計算時間の変化は体感的にありませんでした。ただし、COLMAPの計算時間は画像量に比例して伸びていきそうです。筆者の環境では、300枚の画像からCOLMAPでカメラ位置を計算するのに1時間ほど要しました。 結論 撮影機材が重要 です!! 目印となるもの(被写体や背景)を視界に入れつつ、 カメラがきれいに撮れる範囲でゆっくり動き回りましょう 。 よりクオリティを追求するなら角度を網羅するほうがベターです。 今回検証できなかったこと 本記事では主に動画の撮影方法からNeRFの出力を改善する方法を模索しました。今回検証した項目以外にも、下記のような項目を見直すことで性能の改善が期待できます。 Instant NGP 内の GUI でパラメータチューニング NGP 内ではモデルのパラメータや レンダリング の アルゴリズム などを設定することが可能です。今回はすべてデフォルトとしましたが、より鮮明に表示するためには GUI 上での設定も必要になります。 カメラ位置推定のチューニング floaterやghostの原因となっている画像を見つけて、直接 json から削除が可能です。 結局何枚必要なのか? Instant NGP のTipsには50~150枚が適正との表記がありましたが、多すぎた場合のデメリットはあまり感じられませんでした。被写体によって適正枚数が異なる可能性もあります。 まとめ 今回は、NeRFモデルの入力とする画像を変更することによってどのように出力が変わるのかについて検証を行いました。些細に思える違いでもかなり出力結果は変わってしまい、データの収集や処理にコツが求められると感じました。 NeRFモデル自身は非常に強力なので、 ユースケース やうまく レンダリング できる条件を見極めて使いこなしたいと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ 執筆: @wakamoto.ryosuke 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました ) Instant Neural Graphics Primitives with a Multiresolution Hash Encoding( https://arxiv.org/pdf/2201.05989.pdf ) ↩ NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis( https://arxiv.org/pdf/2003.08934.pdf ) ↩ colmap( https://github.com/colmap/colmap ) ↩ NVIDIA Instant NeRF NGP ( https://github.com/NVlabs/instant-ngp ) ↩
アバター
みなさん、こんにちは! 今回の記事は珍しく(?)以下のメンバーによる共同執筆となります。 金融ソリューション事業部 市場系ソリューション 1 部 寺山 X イノベーション 本部 AI トランスフォーメーションセンター 山田 さて、以前よりアクセスしていただいている方はお気づきかと期待していますが、10 月末に当ブログのデザイン( はてブ ロのテーマ)を刷新しました! ※PC 版のみ適用しています。 変更前 変更後 トップページ 記事ページ 今回はデザインの実装における工夫等を紹介させていただきます! デザイン刷新の目的 デザイン実装の流れ 工夫やこだわりポイント 環境構築 一部のライブラリを変更 サブブログからコピーしたHTMLをホットリロード VSCode の Remote Container を用いて同一環境を再現 執筆者を記事のタイトル下に表示 トップページのカード型レイアウト 記事カードの実装 Lighthouse でのスコア測定 やってみた感想 最後に デザイン刷新の目的 デザインを刷新した経緯/目的は以下のとおりです。 記事や執筆者に一層フォーカスが当たるようにする 当ブログの目的 1 に即し、ISID の魅力のアピールやテックブログから当社の別のチャネルへの導線を強化する モダンでイケてるブログを投稿/運営することでモチベーションを上げる(?) これらの目的やイメージを弊社の UX デザインセンターに伝え、議論した結果新しいデザインを提案してもらいました。 デザインの検討の観点では実際に担当した UX デザインセンターのメンバーが記事を投稿してくれる予定ですので、そちらをお待ちください! デザイン実装の流れ はてなブログ のデザインをカスタマイズする方法はいくつかあるのですが、当ブログでは テーマ を自作した上で適用する方法を採用しました。 テーマを自作する方法は デザインテーマ制作の手引き として公式よりヘルプが公開されています。 詳細は上記リンクで紹介されていますが、私たちがテーマを自作し適用するまでに行った流れは以下のとおりです。 動作確認用のサブブログの開設 動作確認用のエントリを用意 公式の サンプルエントリー に加え、当ブログで公開済みの記事をコピーして動作(デザインの表示)確認に利用しました。 サンプルテーマの Boilerplate リポジトリ をクローンして当社の GitHub でホスト ゴリゴリ実装 詳細は次のセクションで説明します。 サブブログで動作確認 テーマストアに投稿 当ブログに適用 工夫やこだわりポイント 環境構築 はてブ ロの手引きや Boilerplate にもあるとおり、テーマの実体は CSS です。管理者ページにてカスマイズできる HTMLを 含めて意図した修飾がされるように CSS を実装します。 環境構築は主に寺山が担当しました。インフラが主戦場な私は HTML/ CSS に対しほぼ初心者ではあったのですが、環境構築において工夫した点は以下です。 容易に素早く動作確認ができること 知識がない者でも同じ環境を構築可能であること 具体的には以下のような環境としました。 一部のライブラリを変更 Boilerplate で使用している LibSass(node-sass) は deprecated であるため、後継である Dart-Sass(sass) に変更しました。 $ npm uninstall -D node-sass $ npm i -D sass sass-migrator $ npx sass --version 1.51.0 compiled with dart2js 2.16.2 $ npx sass-migrator --version 1.5.4 compiled with dart2js 2.15.1 LibSass から Dart -Sass への移行には Migrator(sass-migrator) を利用しました。 sass-migrator は外部ライブラリのパスを探索できないため、node-sass 記法での import 文を以下のように修正しています。 // 修正前 @import "node_modules/normalize.css/normalize"; // 修正後 @import "normalize.css/normalize"; 以下のようにツールを実行し、 マイグレーション します。 $ npx sass-migrator module --migrate-deps --load-path node_modules scss/isid-modern-theme.scss Boilerplate で定義済みの npm scripts も LibSass から Dart -Sass を実行するように修正します。 { /* 省略 */ " scripts ": { " prestart ": " npm run build ", " start ": " npm-run-all -p watch server ", // LibSassでの定義 "build": "npm-run-all scss autoprefixer", " build ": " npm-run-all sass autoprefixer ", // LibSassでの定義 "scss": "node-sass scss/isid-modern-theme.scss build/isid-modern-theme.css --output-style expanded --indent-width 4 --source-map build/", " sass ": " sass --load-path=node_modules/ scss/isid-modern-theme.scss build/isid-modern-theme.css --source-map ", " autoprefixer ": " postcss --use autoprefixer -r build/isid-modern-theme.css ", " server ": " browser-sync start -c bs-config.js ", " watch ": " chokidar \" scss/ \" -c \" npm run build \" " } , /* 省略 */ } サブブログからコピーしたHTMLをホットリロード 手引きで紹介されているスタイルの確認方法は以下のとおりです。 Boilerplate で定義済みの start コマンドを実行する chokidar で Sass を Watch し、 CSS への コンパイル を実行 Browsersync でローカルサーバを起動し、 コンパイル 後の CSS をホストする はてなブログ のデザイン設定にて、ローカルサーバでホストしている CSS を読み込む こちらの方法では、 はてなブログ のデザインに反映されたことを確認するたび(≒Sass を実装するたび)に、ブログを手動でリロードする必要がありました。 また、デザインの実装は二人で担当していたので、スタイルやカスタム HTMLの変更が競合する懸念もありました。 そこで、当ブログのデザイン実装においては以下としました。 動作確認用のサブブログより HTMLをコピーし、 リポジトリ 内に HTMLファイルとして保存する VSCode の LiveServer 拡張機能 を用いて HTMLをローカルサーバでホストする LiveServer にてホットリロードを実行させて、即座にローカルで確認可能とする 3. について、ブログよりコピーした HTMLを以下のように修正します。 < head > <!-- 省略 --> < link rel = "stylesheet" type = "text/css" href = "https://cdn.blog.st-hatena.com/css/blog.css?version=7e80be95800d540eb0026a4db405b1" /> < link rel = "stylesheet" type = "text/css" href = "build/isid-modern-theme.css" /> <!-- ←ローカルでホストしているCSSを指定 --> <!-- 省略 --> </ head > これにより、chokidar が Sass の変更を Watch して CSS を コンパイル した際に、 CSS を Watch している LiveServer によってホットリロードが行われます。 VSCode の Remote Container を用いて同一環境を再現 各作業者の環境の同一性や手順化する範囲などは状況に依るでしょう。本件に限っては作業者も二人であるため、お互いサポートし合えば済んでしまった可能性は高いです。 しかし、今後のデザインの拡張やテックブログ運営の体制が変わっていく可能性や、そのときの担当者が寺山のように HTML/ CSS の未経験/初心者の可能性もあります。また、 VSCode の Remote Container(devcontainer) を用いれば比較的容易に同一環境を構築するフローは実現可能なので、本件では意識しました。 Remote Container の設定ファイルは以下のとおりです。 { /* ---- コンテナボリューム ---- */ // ① " dockerComposeFile ": " docker-compose.yml ", " service ": " devcontainer-isid-modern-theme ", " workspaceFolder ": " /workspaces ", " postCreateCommand ": " sudo chown -R node:node /workspaces && test -d <リポジトリ名> || git clone https://github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh ", /* ---- 共通 ---- */ " name ": " HatenaTheme ", " settings ": { " liveServer.settings.CustomBrowser ": " chrome ", " eslint.validate ": [ " javascript ", " typescript " ] , " stylelint.validate ": [ " css ", " scss " ] , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } , " css.validate ": false , " scss.validate ": false , " [scss] ": { " editor.defaultFormatter ": " stylelint.vscode-stylelint ", " editor.formatOnSave ": true , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } } , " [css] ": { " editor.defaultFormatter ": " stylelint.vscode-stylelint ", " editor.formatOnSave ": true , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } } , " [typescript] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [javascript] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [html] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [markdown] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " yzhang.markdown-all-in-one " } } , " extensions ": [ " ms-ceintl.vscode-language-pack-ja ", " dbaeumer.vscode-eslint ", " esbenp.prettier-vscode ", " stylelint.vscode-stylelint ", " eamodio.gitlens ", " ritwickdey.LiveServer ", " ms-azuretools.vscode-docker ", " yzhang.markdown-all-in-one ", " bierner.github-markdown-preview " ] , " remoteUser ": " node " } Remote Container にした目的は、言語やランタイムに加え、 VSCode の 拡張機能 やその設定を同一にしつつ手順を簡略化するためです。 上記を実現する方法はさまざまな情報が他にあるため本記事では割愛します。 前のセクションで紹介したとおり、Sass の実装や動作確認では Watcher を利用しています。 弊社の社内標準となっているクライアント PC は Windows なのですが、Remote Container の標準の動作であるローカルホストのボリュームをコンテナにマウントする形式では Watcher が機能しないことが多いです。 確証はないのですが、これはホストとコンテナの ファイルシステム が異なることにより、オーバーヘッドが大きくなっているためであると予想しています。 このことへの対応方法として Use Clone Repository in Container Volume でも紹介されている、コンテナボリュームに リポジトリ をクローンする方式を実施してるのが、上記の設定ファイルの ① の部分です。 Remote Container へ接続した後に リポジトリ のクローンを作業者が手動で行うのはコンテナ化して手順を簡略化していることにならないので、 postCreateCommand を利用してコンテナのビルド時に自動で実行する様にしています。 " postCreateCommand ": " sudo chown - R node : node / workspaces && test - d <リポジトリ名> || git clone https : //github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh" init.sh の内容は以下です。 #!/bin/sh ## Install npm package PKG_JSON = " package.json " if [ -f ${PKG_JSON} ]; then npm install fi SASS_MIGRATOR = " node_modules/sass-migrator/sass-migrator.js " if [ -f ${SASS_MIGRATOR} ]; then chmod a+x ${SASS_MIGRATOR} fi exit 0 この シェルスクリプト を用意している目的は NPM よりパッケージをインストールするためです。 本件に限らず、私は postCreateCommand より スクリプト を実行する方法をよく採用します。処理内容は言語やパッケージマネージャごとに変えています。 執筆者を記事のタイトル下に表示 新しいデザインでは、刷新の目的にも記載した「執筆者にフォーカスが当たる様にする」目的で記事ページに執筆者の プロフィール画像 と執筆者名(執筆者の アーカイブ ページへのリンク)を表示するようにしました。 これは JavaScript を用いて DOM を操作することで実現しています。 ( function () { window .addEventListener( 'DOMContentLoaded' , () => { const author = document .getElementsByClassName( 'author vcard' ) [ 0 ] .querySelector( 'span.fn' ); const blogURL = document .getElementsByTagName( 'html' ) [ 0 ] .getAttribute( 'data-blog-uri' ); const authorAnchor = document .createElement( 'a' ); authorAnchor.href = ` ${blogURL} archive/author/ ${author.innerHTML} ` ; authorAnchor.innerText = author.innerHTML; const p1 = document .createElement( 'p' ); p1.id = 'article-author-link' ; p1.appendChild(authorAnchor); const p2 = document .createElement( 'p' ); p2.id = 'article-author-image' ; const authorImage = document .createElement( 'img' ); authorImage.src = `https://cdn.profile-image.st-hatena.com/users/ ${author.innerHTML} /profile.png` ; p2.appendChild(authorImage); const headerAuthor = document .createElement( 'div' ); headerAuthor.className = 'header-author' ; headerAuthor.appendChild(p2); headerAuthor.appendChild(p1); document .getElementsByClassName( 'entry-title' ) [ 0 ] .after(headerAuthor); } ); } )(); はてなブログ では、カスタム HTMLを記述可能( HTMLを自由記述できる箇所 を参照)なため、 script タグでより柔軟に独自の要素を作成可能です。 上記の JavaScript は、 デザイン設定 > カスタマイズ > 記事 > 記事上HTML(記事本文上) に記述しています。 自分の はてな ID を用いて色々試した結果、以下であることがわかりました。 プロフィール画像 は https://cdn.profile-image.st-hatena.com/users/<はてなID>/profile.png でホストされている ブログメンバーごとの アーカイブ ページは https://tech.isid.co.jp/archive/author/<はてなID> として生成されている したがって、 はてなID が分かればよいことになります。私は記事の下部にある author vcard クラスの span タグが、 はてな ID に一致するものと判断しました。 後はスタイルをどのように記述するかを踏まえて DOM の操作を実装するだけです。 手引きに記載されている様に、HTMLの要素を配置することを禁止している範囲( デザインとコーディングの注意事項 を参照)があるため、DOM を操作して要素を追加する場所には注意が必要です。 現在の当ブログの投稿フローでは、執筆者は全記事が ISID になってしまいます。ゆくゆくは実際に記事を作成したメンバーごとに執筆者名( はてな ID)の表示と アーカイブ ページを提供できるようにしたいと考えています。 トップページのカード型レイアウト 新しいデザインでは、トップページの記事一覧をカード型の コンポーネント で3列に表示するようにしました。 トップページの記事一覧表示自体は、 はてなブログ の設定から簡単にできるのですが、今回のように3列表示にする場合は追加で css を当てる必要があります。 基本的な実装指針としては、 はてなブログ 側で レンダリング されるHTMLのDOMの構造には手を加えず、 css の Flexbox を活用することにしています。 はてなブログ 側で レンダリング される記事一覧部分のHTMLは以下のような構造になっています。 < div class = "archive-entries" > < section class = "archive-entry" > … </ section > < section class = "archive-entry" > … </ section > < section class = "archive-entry" > … </ section > … </ div > 大枠としてのdivタグには .archive-entries クラスが適用されるので、このクラスに display: flex; と flex-direction: row; を当てることで各記事のカードはいい感じに並ぶようになっています。 あとは3列表示しつつ見栄えを整えるために以下の要素を考慮する必要があります。 カード間の余白 ひとつあたりのカードのサイズ カード間の余白については、 css プロパティの gap (grid-gap) を利用しています。 gapはFlexboxやGridなどの行や列を使ったレイアウトをする際にその溝の大きさを定義できるプロパティです。 今回の実装では、 .archive-entries クラスに gap: 24px として設定をしています。 これによって、各記事のカードを配置する際にいい感じに余白を与えてくれます。 ひとつあたりのカードのサイズについては、 css プロパティの flex-basis を利用しています。 Flexboxでは内部に配置される各要素は、よしなに伸長したり縮小しますが、 flex -basisを指定することで各要素のベースとなるサイズを与えることができます。 今回の実装では横幅と余白の関係性から .archive-entry に flex-basis: 314px; として設定をしています。 記事カードの実装 次に、カード型にした記事情報の実装について紹介します。 こちらも基本的な実装指針としては同じく、 はてなブログ 側で レンダリング されるHTMLのDOMの構造には手を加えず、 css の Flexbox を活用することにしています。 まず記事カードに含める情報を整理します。今回、記事カードに含める情報と表示したい要素の順は以下のとおりです。 サムネイル画像 記事の公開日時 記事のタイトル 記事の概要 記事のカテゴリ そして、 はてなブログ 側で レンダリング される記事カード部分のHTMLは以下のような構造になっています。 < section class = "archive-entry" > < div class = "archive-entry-header" > < div class = "date archive-date" > … </ div >           < h1 class = "entry-title" > … </ h1 > </ div > < div class = "categories" > … </ div > < a href = "#" class = "entry-thumb-link" > < div class = "entry-thumb" style = "background-image: url('#');" ></ div > </ a > < div class = "archive-entry-body" > … </ div > </ section > こちらも大枠としてのdivタグに .archive-entry クラスが適応されるので、このクラスに display: flex; とします。 そしてまず、考慮しなければならないのは表示する要素の順番です。 はてなブログ 側で レンダリング されるHTMLをそのまま表示しようとすると要素は以下の順番になります。 記事の公開日時 記事のタイトル 記事のカテゴリ サムネイル画像 記事の概要 これは先程の表示したい要素の順番と異なります。 こういった場面で便利なのがFlexboxの order プロパティです。 FLexbox内でorderプロパティを持つ要素は、その値が小さいものから順番に並んで表示されます。 今回は以下のようにorderプロパティを当てて順番を制御しています。 /* 記事のタイトルおよび記事の公開日時 */ .archive-entry-header { order : 1 ; } /* 記事の概要 */ .archive-entry-body { order : 2 ; } /* 記事のカテゴリ */ .categories { order : 3 ; } これによって はてなブログ 側で レンダリング されるHTMLのDOM構造を変えることなく、もともと表示させたかった要素の順に見た目を整えています。 サムネイル画像のaタグの要素については、 タブレット サイズでのレイアウトとの兼ね合いもあり、現時点ではorderプロパティで制御していません。 この実装の注意点としては、orderプロパティは アクセシビリティ に影響を与える点があります。 音声読み上げソフトなどはもとの構造に従って、実行されるのためDOM構造そのものを変更できる場合は、きちんと意図の通りの順番に レンダリング するのが良いでしょう。 order プロパティと アクセシビリティ 、 https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#order_%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B7%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3 Lighthouse でのスコア測定 新デザインを適応前後に Lighthouse を使ってスコア測定をしました。 新デザイン適応前のスコア(2022/10/11 時点) 新デザイン適応後のスコア(2022/11/25 時点) パフォーマンスのスコアは環境によっても変化するので、あくまで参考値ですが、新デザイン適応後は アクセシビリティ やベストプ ラク ティスについてはスコアが改善しました。 特に アクセシビリティ の部分は配色や コントラ ストによって評価される項目のため、今回のデザイン刷新にあたりデザイナーの藤崎さんと協力して取り組めたことが大きかったと考えています。 やってみた感想 寺山 普段はインフラエンジニア/ クラウド アーキテクトとしてアプリケーションをホストする基盤の開発を業務としているので、 CSS とはいえ実際に目に見えるものを実装する作業は新鮮で楽しかったです。初心者であったため苦戦もしましたし、実際にかなり山田さんに助けてもらいましたが(汗) React 等のフロントエンド フレームワーク が主流になった現在において、 CSS 記法を業務で直接活用する機会は少ないと予想しています。一方、 Markdown や SSG にちょっとデザインを加えたい場合などでは CSS が利用できるツールも多いので、今回身につけた知識を役立てていきたいと考えています! 山田 私も普段の業務では Python を書く機会が多く、 CSS を書いてデザインを整える作業は学生時代のアルバイト以来でした。 はてなブログ 側で レンダリング されるDOMの構造を保つことを意識しての、実装作業は思いの外大変で、 はてなブログ のデザインテーマを作成している方々は凄いなと素直に思いました。 今回の実装では、まだ実現できなかった部分や軽微な修正が必要な部分があるので日々改善を続けていきたいですね。 最後に 当ブログを通じて弊社の魅力がよりみなさんに伝わるよう、ブログのデザインも引き続き改善していく予定です。また、 ソースコード の リポジトリ や、 はてなブログ のテーマをテーマストア上で公開するかもしれません。 記事の中身が一番の主役ではありますが、ブログのデザインにも時々着目していただければ幸いです! 執筆: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました ) ISID テックブログの狙いについては、 ISIDがテックブログを始めた理由 、 テックブログ始めました。 をご覧ください。 ↩
アバター
みなさん、こんにちは! 今回の記事は珍しく(?)以下のメンバーによる共同執筆となります。 金融ソリューション事業部 市場系ソリューション 1 部 寺山 X イノベーション 本部 AI トランスフォーメーションセンター 山田 さて、以前よりアクセスしていただいている方はお気づきかと期待していますが、10 月末に当ブログのデザイン( はてブ ロのテーマ)を刷新しました! ※PC 版のみ適用しています。 変更前 変更後 トップページ 記事ページ 今回はデザインの実装における工夫等を紹介させていただきます! デザイン刷新の目的 デザイン実装の流れ 工夫やこだわりポイント 環境構築 一部のライブラリを変更 サブブログからコピーしたHTMLをホットリロード VSCode の Remote Container を用いて同一環境を再現 執筆者を記事のタイトル下に表示 トップページのカード型レイアウト 記事カードの実装 Lighthouse でのスコア測定 やってみた感想 最後に デザイン刷新の目的 デザインを刷新した経緯/目的は以下のとおりです。 記事や執筆者に一層フォーカスが当たるようにする 当ブログの目的 1 に即し、ISID の魅力のアピールやテックブログから当社の別のチャネルへの導線を強化する モダンでイケてるブログを投稿/運営することでモチベーションを上げる(?) これらの目的やイメージを弊社の UX デザインセンターに伝え、議論した結果新しいデザインを提案してもらいました。 デザインの検討の観点では実際に担当した UX デザインセンターのメンバーが記事を投稿してくれる予定ですので、そちらをお待ちください! デザイン実装の流れ はてなブログ のデザインをカスタマイズする方法はいくつかあるのですが、当ブログでは テーマ を自作した上で適用する方法を採用しました。 テーマを自作する方法は デザインテーマ制作の手引き として公式よりヘルプが公開されています。 詳細は上記リンクで紹介されていますが、私たちがテーマを自作し適用するまでに行った流れは以下のとおりです。 動作確認用のサブブログの開設 動作確認用のエントリを用意 公式の サンプルエントリー に加え、当ブログで公開済みの記事をコピーして動作(デザインの表示)確認に利用しました。 サンプルテーマの Boilerplate リポジトリ をクローンして当社の GitHub でホスト ゴリゴリ実装 詳細は次のセクションで説明します。 サブブログで動作確認 テーマストアに投稿 当ブログに適用 工夫やこだわりポイント 環境構築 はてブ ロの手引きや Boilerplate にもあるとおり、テーマの実体は CSS です。管理者ページにてカスマイズできる HTMLを 含めて意図した修飾がされるように CSS を実装します。 環境構築は主に寺山が担当しました。インフラが主戦場な私は HTML/ CSS に対しほぼ初心者ではあったのですが、環境構築において工夫した点は以下です。 容易に素早く動作確認ができること 知識がない者でも同じ環境を構築可能であること 具体的には以下のような環境としました。 一部のライブラリを変更 Boilerplate で使用している LibSass(node-sass) は deprecated であるため、後継である Dart-Sass(sass) に変更しました。 $ npm uninstall -D node-sass $ npm i -D sass sass-migrator $ npx sass --version 1.51.0 compiled with dart2js 2.16.2 $ npx sass-migrator --version 1.5.4 compiled with dart2js 2.15.1 LibSass から Dart -Sass への移行には Migrator(sass-migrator) を利用しました。 sass-migrator は外部ライブラリのパスを探索できないため、node-sass 記法での import 文を以下のように修正しています。 // 修正前 @import "node_modules/normalize.css/normalize"; // 修正後 @import "normalize.css/normalize"; 以下のようにツールを実行し、 マイグレーション します。 $ npx sass-migrator module --migrate-deps --load-path node_modules scss/isid-modern-theme.scss Boilerplate で定義済みの npm scripts も LibSass から Dart -Sass を実行するように修正します。 { /* 省略 */ " scripts ": { " prestart ": " npm run build ", " start ": " npm-run-all -p watch server ", // LibSassでの定義 "build": "npm-run-all scss autoprefixer", " build ": " npm-run-all sass autoprefixer ", // LibSassでの定義 "scss": "node-sass scss/isid-modern-theme.scss build/isid-modern-theme.css --output-style expanded --indent-width 4 --source-map build/", " sass ": " sass --load-path=node_modules/ scss/isid-modern-theme.scss build/isid-modern-theme.css --source-map ", " autoprefixer ": " postcss --use autoprefixer -r build/isid-modern-theme.css ", " server ": " browser-sync start -c bs-config.js ", " watch ": " chokidar \" scss/ \" -c \" npm run build \" " } , /* 省略 */ } サブブログからコピーしたHTMLをホットリロード 手引きで紹介されているスタイルの確認方法は以下のとおりです。 Boilerplate で定義済みの start コマンドを実行する chokidar で Sass を Watch し、 CSS への コンパイル を実行 Browsersync でローカルサーバを起動し、 コンパイル 後の CSS をホストする はてなブログ のデザイン設定にて、ローカルサーバでホストしている CSS を読み込む こちらの方法では、 はてなブログ のデザインに反映されたことを確認するたび(≒Sass を実装するたび)に、ブログを手動でリロードする必要がありました。 また、デザインの実装は二人で担当していたので、スタイルやカスタム HTMLの変更が競合する懸念もありました。 そこで、当ブログのデザイン実装においては以下としました。 動作確認用のサブブログより HTMLをコピーし、 リポジトリ 内に HTMLファイルとして保存する VSCode の LiveServer 拡張機能 を用いて HTMLをローカルサーバでホストする LiveServer にてホットリロードを実行させて、即座にローカルで確認可能とする 3. について、ブログよりコピーした HTMLを以下のように修正します。 < head > <!-- 省略 --> < link rel = "stylesheet" type = "text/css" href = "https://cdn.blog.st-hatena.com/css/blog.css?version=7e80be95800d540eb0026a4db405b1" /> < link rel = "stylesheet" type = "text/css" href = "build/isid-modern-theme.css" /> <!-- ←ローカルでホストしているCSSを指定 --> <!-- 省略 --> </ head > これにより、chokidar が Sass の変更を Watch して CSS を コンパイル した際に、 CSS を Watch している LiveServer によってホットリロードが行われます。 VSCode の Remote Container を用いて同一環境を再現 各作業者の環境の同一性や手順化する範囲などは状況に依るでしょう。本件に限っては作業者も二人であるため、お互いサポートし合えば済んでしまった可能性は高いです。 しかし、今後のデザインの拡張やテックブログ運営の体制が変わっていく可能性や、そのときの担当者が寺山のように HTML/ CSS の未経験/初心者の可能性もあります。また、 VSCode の Remote Container(devcontainer) を用いれば比較的容易に同一環境を構築するフローは実現可能なので、本件では意識しました。 Remote Container の設定ファイルは以下のとおりです。 { /* ---- コンテナボリューム ---- */ // ① " dockerComposeFile ": " docker-compose.yml ", " service ": " devcontainer-isid-modern-theme ", " workspaceFolder ": " /workspaces ", " postCreateCommand ": " sudo chown -R node:node /workspaces && test -d <リポジトリ名> || git clone https://github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh ", /* ---- 共通 ---- */ " name ": " HatenaTheme ", " settings ": { " liveServer.settings.CustomBrowser ": " chrome ", " eslint.validate ": [ " javascript ", " typescript " ] , " stylelint.validate ": [ " css ", " scss " ] , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } , " css.validate ": false , " scss.validate ": false , " [scss] ": { " editor.defaultFormatter ": " stylelint.vscode-stylelint ", " editor.formatOnSave ": true , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } } , " [css] ": { " editor.defaultFormatter ": " stylelint.vscode-stylelint ", " editor.formatOnSave ": true , " editor.codeActionsOnSave ": { " source.fixAll.stylelint ": true } } , " [typescript] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [javascript] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [html] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " esbenp.prettier-vscode " } , " [markdown] ": { " editor.formatOnSave ": true , " editor.defaultFormatter ": " yzhang.markdown-all-in-one " } } , " extensions ": [ " ms-ceintl.vscode-language-pack-ja ", " dbaeumer.vscode-eslint ", " esbenp.prettier-vscode ", " stylelint.vscode-stylelint ", " eamodio.gitlens ", " ritwickdey.LiveServer ", " ms-azuretools.vscode-docker ", " yzhang.markdown-all-in-one ", " bierner.github-markdown-preview " ] , " remoteUser ": " node " } Remote Container にした目的は、言語やランタイムに加え、 VSCode の 拡張機能 やその設定を同一にしつつ手順を簡略化するためです。 上記を実現する方法はさまざまな情報が他にあるため本記事では割愛します。 前のセクションで紹介したとおり、Sass の実装や動作確認では Watcher を利用しています。 弊社の社内標準となっているクライアント PC は Windows なのですが、Remote Container の標準の動作であるローカルホストのボリュームをコンテナにマウントする形式では Watcher が機能しないことが多いです。 確証はないのですが、これはホストとコンテナの ファイルシステム が異なることにより、オーバーヘッドが大きくなっているためであると予想しています。 このことへの対応方法として Use Clone Repository in Container Volume でも紹介されている、コンテナボリュームに リポジトリ をクローンする方式を実施してるのが、上記の設定ファイルの ① の部分です。 Remote Container へ接続した後に リポジトリ のクローンを作業者が手動で行うのはコンテナ化して手順を簡略化していることにならないので、 postCreateCommand を利用してコンテナのビルド時に自動で実行する様にしています。 " postCreateCommand ": " sudo chown - R node : node / workspaces && test - d <リポジトリ名> || git clone https : //github.com/ISID/<リポジトリ名>.git ; cd <リポジトリ名>/ && chmod a+x .devcontainer/init.sh && .devcontainer/init.sh" init.sh の内容は以下です。 #!/bin/sh ## Install npm package PKG_JSON = " package.json " if [ -f ${PKG_JSON} ]; then npm install fi SASS_MIGRATOR = " node_modules/sass-migrator/sass-migrator.js " if [ -f ${SASS_MIGRATOR} ]; then chmod a+x ${SASS_MIGRATOR} fi exit 0 この シェルスクリプト を用意している目的は NPM よりパッケージをインストールするためです。 本件に限らず、私は postCreateCommand より スクリプト を実行する方法をよく採用します。処理内容は言語やパッケージマネージャごとに変えています。 執筆者を記事のタイトル下に表示 新しいデザインでは、刷新の目的にも記載した「執筆者にフォーカスが当たる様にする」目的で記事ページに執筆者の プロフィール画像 と執筆者名(執筆者の アーカイブ ページへのリンク)を表示するようにしました。 これは JavaScript を用いて DOM を操作することで実現しています。 ( function () { window .addEventListener( 'DOMContentLoaded' , () => { const author = document .getElementsByClassName( 'author vcard' ) [ 0 ] .querySelector( 'span.fn' ); const blogURL = document .getElementsByTagName( 'html' ) [ 0 ] .getAttribute( 'data-blog-uri' ); const authorAnchor = document .createElement( 'a' ); authorAnchor.href = ` ${blogURL} archive/author/ ${author.innerHTML} ` ; authorAnchor.innerText = author.innerHTML; const p1 = document .createElement( 'p' ); p1.id = 'article-author-link' ; p1.appendChild(authorAnchor); const p2 = document .createElement( 'p' ); p2.id = 'article-author-image' ; const authorImage = document .createElement( 'img' ); authorImage.src = `https://cdn.profile-image.st-hatena.com/users/ ${author.innerHTML} /profile.png` ; p2.appendChild(authorImage); const headerAuthor = document .createElement( 'div' ); headerAuthor.className = 'header-author' ; headerAuthor.appendChild(p2); headerAuthor.appendChild(p1); document .getElementsByClassName( 'entry-title' ) [ 0 ] .after(headerAuthor); } ); } )(); はてなブログ では、カスタム HTMLを記述可能( HTMLを自由記述できる箇所 を参照)なため、 script タグでより柔軟に独自の要素を作成可能です。 上記の JavaScript は、 デザイン設定 > カスタマイズ > 記事 > 記事上HTML(記事本文上) に記述しています。 自分の はてな ID を用いて色々試した結果、以下であることがわかりました。 プロフィール画像 は https://cdn.profile-image.st-hatena.com/users/<はてなID>/profile.png でホストされている ブログメンバーごとの アーカイブ ページは https://tech.isid.co.jp/archive/author/<はてなID> として生成されている したがって、 はてなID が分かればよいことになります。私は記事の下部にある author vcard クラスの span タグが、 はてな ID に一致するものと判断しました。 後はスタイルをどのように記述するかを踏まえて DOM の操作を実装するだけです。 手引きに記載されている様に、HTMLの要素を配置することを禁止している範囲( デザインとコーディングの注意事項 を参照)があるため、DOM を操作して要素を追加する場所には注意が必要です。 現在の当ブログの投稿フローでは、執筆者は全記事が ISID になってしまいます。ゆくゆくは実際に記事を作成したメンバーごとに執筆者名( はてな ID)の表示と アーカイブ ページを提供できるようにしたいと考えています。 トップページのカード型レイアウト 新しいデザインでは、トップページの記事一覧をカード型の コンポーネント で3列に表示するようにしました。 トップページの記事一覧表示自体は、 はてなブログ の設定から簡単にできるのですが、今回のように3列表示にする場合は追加で css を当てる必要があります。 基本的な実装指針としては、 はてなブログ 側で レンダリング されるHTMLのDOMの構造には手を加えず、 css の Flexbox を活用することにしています。 はてなブログ 側で レンダリング される記事一覧部分のHTMLは以下のような構造になっています。 < div class = "archive-entries" > < section class = "archive-entry" > … </ section > < section class = "archive-entry" > … </ section > < section class = "archive-entry" > … </ section > … </ div > 大枠としてのdivタグには .archive-entries クラスが適用されるので、このクラスに display: flex; と flex-direction: row; を当てることで各記事のカードはいい感じに並ぶようになっています。 あとは3列表示しつつ見栄えを整えるために以下の要素を考慮する必要があります。 カード間の余白 ひとつあたりのカードのサイズ カード間の余白については、 css プロパティの gap (grid-gap) を利用しています。 gapはFlexboxやGridなどの行や列を使ったレイアウトをする際にその溝の大きさを定義できるプロパティです。 今回の実装では、 .archive-entries クラスに gap: 24px として設定をしています。 これによって、各記事のカードを配置する際にいい感じに余白を与えてくれます。 ひとつあたりのカードのサイズについては、 css プロパティの flex-basis を利用しています。 Flexboxでは内部に配置される各要素は、よしなに伸長したり縮小しますが、 flex -basisを指定することで各要素のベースとなるサイズを与えることができます。 今回の実装では横幅と余白の関係性から .archive-entry に flex-basis: 314px; として設定をしています。 記事カードの実装 次に、カード型にした記事情報の実装について紹介します。 こちらも基本的な実装指針としては同じく、 はてなブログ 側で レンダリング されるHTMLのDOMの構造には手を加えず、 css の Flexbox を活用することにしています。 まず記事カードに含める情報を整理します。今回、記事カードに含める情報と表示したい要素の順は以下のとおりです。 サムネイル画像 記事の公開日時 記事のタイトル 記事の概要 記事のカテゴリ そして、 はてなブログ 側で レンダリング される記事カード部分のHTMLは以下のような構造になっています。 < section class = "archive-entry" > < div class = "archive-entry-header" > < div class = "date archive-date" > … </ div >           < h1 class = "entry-title" > … </ h1 > </ div > < div class = "categories" > … </ div > < a href = "#" class = "entry-thumb-link" > < div class = "entry-thumb" style = "background-image: url('#');" ></ div > </ a > < div class = "archive-entry-body" > … </ div > </ section > こちらも大枠としてのdivタグに .archive-entry クラスが適応されるので、このクラスに display: flex; とします。 そしてまず、考慮しなければならないのは表示する要素の順番です。 はてなブログ 側で レンダリング されるHTMLをそのまま表示しようとすると要素は以下の順番になります。 記事の公開日時 記事のタイトル 記事のカテゴリ サムネイル画像 記事の概要 これは先程の表示したい要素の順番と異なります。 こういった場面で便利なのがFlexboxの order プロパティです。 FLexbox内でorderプロパティを持つ要素は、その値が小さいものから順番に並んで表示されます。 今回は以下のようにorderプロパティを当てて順番を制御しています。 /* 記事のタイトルおよび記事の公開日時 */ .archive-entry-header { order : 1 ; } /* 記事の概要 */ .archive-entry-body { order : 2 ; } /* 記事のカテゴリ */ .categories { order : 3 ; } これによって はてなブログ 側で レンダリング されるHTMLのDOM構造を変えることなく、もともと表示させたかった要素の順に見た目を整えています。 サムネイル画像のaタグの要素については、 タブレット サイズでのレイアウトとの兼ね合いもあり、現時点ではorderプロパティで制御していません。 この実装の注意点としては、orderプロパティは アクセシビリティ に影響を与える点があります。 音声読み上げソフトなどはもとの構造に従って、実行されるのためDOM構造そのものを変更できる場合は、きちんと意図の通りの順番に レンダリング するのが良いでしょう。 order プロパティと アクセシビリティ 、 https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#order_%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B7%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3 Lighthouse でのスコア測定 新デザインを適応前後に Lighthouse を使ってスコア測定をしました。 新デザイン適応前のスコア(2022/10/11 時点) 新デザイン適応後のスコア(2022/11/25 時点) パフォーマンスのスコアは環境によっても変化するので、あくまで参考値ですが、新デザイン適応後は アクセシビリティ やベストプ ラク ティスについてはスコアが改善しました。 特に アクセシビリティ の部分は配色や コントラ ストによって評価される項目のため、今回のデザイン刷新にあたりデザイナーの藤崎さんと協力して取り組めたことが大きかったと考えています。 やってみた感想 寺山 普段はインフラエンジニア/ クラウド アーキテクトとしてアプリケーションをホストする基盤の開発を業務としているので、 CSS とはいえ実際に目に見えるものを実装する作業は新鮮で楽しかったです。初心者であったため苦戦もしましたし、実際にかなり山田さんに助けてもらいましたが(汗) React 等のフロントエンド フレームワーク が主流になった現在において、 CSS 記法を業務で直接活用する機会は少ないと予想しています。一方、 Markdown や SSG にちょっとデザインを加えたい場合などでは CSS が利用できるツールも多いので、今回身につけた知識を役立てていきたいと考えています! 山田 私も普段の業務では Python を書く機会が多く、 CSS を書いてデザインを整える作業は学生時代のアルバイト以来でした。 はてなブログ 側で レンダリング されるDOMの構造を保つことを意識しての、実装作業は思いの外大変で、 はてなブログ のデザインテーマを作成している方々は凄いなと素直に思いました。 今回の実装では、まだ実現できなかった部分や軽微な修正が必要な部分があるので日々改善を続けていきたいですね。 最後に 当ブログを通じて弊社の魅力がよりみなさんに伝わるよう、ブログのデザインも引き続き改善していく予定です。また、 ソースコード の リポジトリ や、 はてなブログ のテーマをテーマストア上で公開するかもしれません。 記事の中身が一番の主役ではありますが、ブログのデザインにも時々着目していただければ幸いです! 執筆: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました ) ISID テックブログの狙いについては、 ISIDがテックブログを始めた理由 、 テックブログ始めました。 をご覧ください。 ↩
アバター
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 先日、TECHPLAYでDataflowに関連するお話をしました。 その日の模様が公開されていますので、ご興味のある方は是非ご覧ください。 techplay.jp さて、このエントリではBigQueryにおいて利用できる 再帰 クエリ(WITH RECURSIVE句とUNION ALL)について紹介します。 再帰 クエリを使うと、表形式しか扱えない RDB において 木構造 のデータを扱えます。 木構造 のデータとは、例えば会社における組織図のようなものを想像してください。 ちなみに、 Oracle なら 再帰 クエリはSTART WITH句とCONNECT BY句で実現していましたよね。 前提となるデータ データを定義するクエリ データを投入するクエリ 一番簡単な再帰クエリ 再帰クエリに配列を導入する 再帰クエリに導入した配列のオフセット位置を使う 再帰クエリと条件分岐を組み合わせる 再帰クエリと副問い合わせを組み合わせる 再帰クエリに導入した配列をソースに副問い合わせする まとめ 前提となるデータ 再帰 クエリの話を始める前に、まずはこのエントリで取り扱うデータ構造について説明しておきましょう。 今回の説明に使うのは以下のような組織図をデータベースに格納したものです。 二つの事業部があり、それぞれに事業部長(いろは、にほへ)がいます。 事業部長が統括する事業部には、それぞれの部署には部長がいます。日本語事業部にはカタカナ部と数字部があり、英語事業部にはアルファベット部とナンバー部があります。 部長の下には副部長がいる部門(カタカナ部、アルファベット部)とそうでない部門(数字部、ナンバー部)があります。 副部長の有無に関わらず、部門内にはいくつかの課があり、それぞれに課長がいます。 データを定義するクエリ それでは、BigQueryに投入可能なCREATE TABLE文をお見せしましょう。 create or replace table taichi_test.organization ( # 1. id int64 not null, # 2. parent int64, # 3. department string, # 4. title string not null, # 5. name string not null # 6. ); taichi_test については、読者の皆さんがご自分で用意したデー タセット 名を指定してください。 idカラムはこのテーブルのプライマリキーとなるものです。なお、BigQueryに一意制約はありません。 各レコードが親となるレコードのidを指定するためのカラムです。親がいない事業部長はnullになります。例えば、部長の親レコードは事業部長です。 部署名を格納するカラムです。事業部長の部署名は事業部名になります。 図の中では箱の一行目に書かれている役職名を格納するカラムです。 図の中では箱の二行目に書かれている社員名を格納するカラムです。 データを投入するクエリ データを投入するためのINSERT文です。 insert into taichi_test.organization values (1, null, "日本語事業部", "事業部長", "いろは") , (2, 1, "カタカナ部", "部長", "ア") , (3, 2, "カタカナ部", "副部長", "イ") , (4, 2, "カタカナ部", "副部長", "ウ") , (5, 3, "カタカナ部", "課長", "エ") , (6, 3, "カタカナ部", "課長", "オ") , (7, 3, "カタカナ部", "課長", "カ") , (8, 4, "カタカナ部", "課長", "キ") , (9, 4, "カタカナ部", "課長", "ク") , (10, 1, "数字部", "部長", "一") , (11, 10, "数字部", "課長", "二") , (12, 10, "数字部", "課長", "三") , (13, 10, "数字部", "課長", "四") , (14, 10, "数字部", "課長", "五") , (15, null, "英語事業部", "事業部長", "にほへ") , (16, 15, "アルファベット部", "部長", "A") , (17, 16, "アルファベット部", "副部長", "B") , (18, 17, "アルファベット部", "課長", "C") , (19, 17, "アルファベット部", "課長", "D") , (20, 17, "アルファベット部", "課長", "E") , (21, 15, "ナンバー部", "部長", "1") , (22, 21, "ナンバー部", "課長", "2") , (23, 21, "ナンバー部", "課長", "3"); BigQueryではINSERT文を複数並べるよりも、このようにBulk INSERTをすると高速にデータの投入が終わります。 一番簡単な 再帰 クエリ では 再帰 クエリを実行していきましょう。 再帰 クエリの詳細なリファレンスはBigQueryの RECURSIVE キーワード を参照してください。 with recursive org_rec as ( select id ,parent, department, title, name, "" as manager # 1 . from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, a.name as manager # 2 . from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, manager from org_rec where id < 15 order by id; このクエリでは、部署名、役職名、社員名に加えて、当該社員のマネージャーが誰なのかを検索します。 再帰 クエリではカラム数を合わせる必要があり、親レコードがない場合には空文字を選択しています。 再帰 クエリにおける上位レコードから社員名を選択しています。これによってマネージャーが誰なのか分かるのです。 この SQL の実行結果を見てみましょう。 部長である ア さんと 一 さんのmanagerとして いろは さんが選択されていますね。 副部長や、課長の皆さんについても、対応するマネージャーが選択されています。 再帰 クエリに配列を導入する 次は 再帰 クエリから得られる 木構造 において、親に向かう経路を分析してみましょう。 それには、 再帰 しながら配列を組み立てていく SQL を発行します。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, array_length(path) as level, # 3. array_to_string(path, ", ") as route # 4. from org_rec where id < 15 order by id; 配列を社員名で初期化します。 上位階層から来た配列に現在の階層における社員名を追加します。 array_length関数を使って配列の長さを得ることで階層における位置を選択しています。 各社員から階層をたどる経路は、array_to_string関数を使って配列を結合すると得られます。 この SQL の実行結果を見てみましょう。 例えば、8行目の キ さんについて選択されたレコードを見てください。 組織図で キ さんから いろは 事業部長までの経路を確認するとこのようになっています。 いろは 事業部長から見て キ さんは ア 部長の部下である ウ 副部長の部下ですね。 つまり、levelカラムが示すように4層目にいて、その経路は「いろは→ア→ウ→キ」となるわけです。 再帰 クエリに導入した配列のオフセット位置を使う 再帰 クエリで 木構造 をたどるには配列を使うと説明しましたが、配列なのでオフセット位置を直接指定できます。 例えば、以下のようなクエリが考えられます。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select path[offset(0)] as bigboss, # 1. count(path[offset(0)]) as members # 2. from org_rec group by bigboss # 3. 各社員の列にある0番目の要素には必ず事業部長が入っています。 レコードとして事業部長が出現する回数を数えています。 事業部長でグルーピングしています。 この SQL の実行結果を見てみましょう。 各事業部長に連なる社員が事業部長本人を含めて何名なのか選択されていますね。 再帰 クエリと条件分岐を組み合わせる 再帰 クエリで作った配列のオフセット位置を使ってマネージャーを選択してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, path[offset(array_length(path)-2)] as boss # 1. from org_rec order by id; オフセット位置として配列の長さから2を引いた値を使っています。 これを実行すると以下のようなエラーになって期待した通りには動作しません。 Array index -1 is out of bounds (underflow) 事業部長はマネージャーがいないので配列の長さが1です。それによってこのようなエラーになる訳です。 ここでは、case句を使って問題に対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, (case when array_length(path) < 2 then null # 1. else path[offset(array_length(path)-2)] # 2. end) as manager from org_rec where id < 15 order by id; 配列の長さが2を下回るものについてはnullを選択しています。 配列の長さが2を下回らないものについては、長さから2を引いた値を指定することでマネージャーを選択しています。 この SQL の実行結果を見てみましょう。 最初に説明したクエリと同じ結果が得られていますね。 再帰 クエリと副問い合わせを組み合わせる 次は、 再帰 クエリで作った配列を使って各社員にとっての事業部長を選択してみましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, (select name from `taichi_test.organization` where id = path[offset(0)] # 3. ) as bigboss from org_rec where id < 15 order by id; 配列をidで初期化します。 上位階層から来た配列に現在の階層におけるidを追加します。 各レコードに含まれる配列の0番目は必ず事業部長のidが入っています。つまり、特に条件を指定せずにオフセット位置を指定できます。 この SQL の実行結果を見てみましょう。 bigboss列に いろは 事業部長が入っていますね。事業部長だけは自己参照しており奇妙な状態になっていますが、これに対する対処は皆さん、ご自分でやってみてください。 説明のためにSELECT句の中で副問い合わせをしましたが、あまり褒められたものではありません。こういう時は本来INNER JOINを使って情報を付加しましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select a.title, a.name, b.name as bigboss from org_rec as a inner join `taichi_test.organization` as b on a.path[offset(0)] = b.id where a.id < 15 order by a.id; 再帰 クエリに導入した配列をソースに副問い合わせする 最後は、もう少し複雑なクエリを実行してみましょう。 所で、最初のCREATE TABLE文の説明に奇妙な部分があったのはお気づきでしょうか? departmentカラムに入るデータが、事業部長を表すレコードの時だけ事業部名が入っています。 本来的には単一のカラムに複数の意味合いを持たせることは望ましくないのですが、ついうっかりデータを圧縮するためにやってしまいました。 私の手抜きによるやらかしに、データの洗い替えをせずに SQL だけで対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select (select department from `taichi_test.organization` inner join ( select level from unnest(path) as level with offset where offset = 0 # 1. ) as levels on id = levels.level) as unit, department, title, name from org_rec order by id; BigQueryではunnest関数を使うと配列をテーブルであるかのように扱えます。また、併せてwith offset句を使うとクエリの中で配列のオフセット位置を参照できます。 この SQL の実行結果を見てみましょう。 各レコードに事業部名が選択できていますね。 まとめ 今回エントリでは、BigQueryにおける 再帰 クエリの使い方について詳しく説明しました。 再帰 クエリを使う上で配列と組み合わせることはある種のイディオムなのですが、あまり知られていません。 これを知っているだけで、データ分析の幅は確実に広がりますので是非使いこなしてください。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
みなさんこんにちは、 電通国際情報サービス (ISID)コーポレート本部 システム推進部の佐藤太一です。 先日、TECHPLAYでDataflowに関連するお話をしました。 その日の模様が公開されていますので、ご興味のある方は是非ご覧ください。 techplay.jp さて、このエントリではBigQueryにおいて利用できる 再帰 クエリ(WITH RECURSIVE句とUNION ALL)について紹介します。 再帰 クエリを使うと、表形式しか扱えない RDB において 木構造 のデータを扱えます。 木構造 のデータとは、例えば会社における組織図のようなものを想像してください。 ちなみに、 Oracle なら 再帰 クエリはSTART WITH句とCONNECT BY句で実現していましたよね。 前提となるデータ データを定義するクエリ データを投入するクエリ 一番簡単な再帰クエリ 再帰クエリに配列を導入する 再帰クエリに導入した配列のオフセット位置を使う 再帰クエリと条件分岐を組み合わせる 再帰クエリと副問い合わせを組み合わせる 再帰クエリに導入した配列をソースに副問い合わせする まとめ 前提となるデータ 再帰 クエリの話を始める前に、まずはこのエントリで取り扱うデータ構造について説明しておきましょう。 今回の説明に使うのは以下のような組織図をデータベースに格納したものです。 二つの事業部があり、それぞれに事業部長(いろは、にほへ)がいます。 事業部長が統括する事業部には、それぞれの部署には部長がいます。日本語事業部にはカタカナ部と数字部があり、英語事業部にはアルファベット部とナンバー部があります。 部長の下には副部長がいる部門(カタカナ部、アルファベット部)とそうでない部門(数字部、ナンバー部)があります。 副部長の有無に関わらず、部門内にはいくつかの課があり、それぞれに課長がいます。 データを定義するクエリ それでは、BigQueryに投入可能なCREATE TABLE文をお見せしましょう。 create or replace table taichi_test.organization ( # 1. id int64 not null, # 2. parent int64, # 3. department string, # 4. title string not null, # 5. name string not null # 6. ); taichi_test については、読者の皆さんがご自分で用意したデー タセット 名を指定してください。 idカラムはこのテーブルのプライマリキーとなるものです。なお、BigQueryに一意制約はありません。 各レコードが親となるレコードのidを指定するためのカラムです。親がいない事業部長はnullになります。例えば、部長の親レコードは事業部長です。 部署名を格納するカラムです。事業部長の部署名は事業部名になります。 図の中では箱の一行目に書かれている役職名を格納するカラムです。 図の中では箱の二行目に書かれている社員名を格納するカラムです。 データを投入するクエリ データを投入するためのINSERT文です。 insert into taichi_test.organization values (1, null, "日本語事業部", "事業部長", "いろは") , (2, 1, "カタカナ部", "部長", "ア") , (3, 2, "カタカナ部", "副部長", "イ") , (4, 2, "カタカナ部", "副部長", "ウ") , (5, 3, "カタカナ部", "課長", "エ") , (6, 3, "カタカナ部", "課長", "オ") , (7, 3, "カタカナ部", "課長", "カ") , (8, 4, "カタカナ部", "課長", "キ") , (9, 4, "カタカナ部", "課長", "ク") , (10, 1, "数字部", "部長", "一") , (11, 10, "数字部", "課長", "二") , (12, 10, "数字部", "課長", "三") , (13, 10, "数字部", "課長", "四") , (14, 10, "数字部", "課長", "五") , (15, null, "英語事業部", "事業部長", "にほへ") , (16, 15, "アルファベット部", "部長", "A") , (17, 16, "アルファベット部", "副部長", "B") , (18, 17, "アルファベット部", "課長", "C") , (19, 17, "アルファベット部", "課長", "D") , (20, 17, "アルファベット部", "課長", "E") , (21, 15, "ナンバー部", "部長", "1") , (22, 21, "ナンバー部", "課長", "2") , (23, 21, "ナンバー部", "課長", "3"); BigQueryではINSERT文を複数並べるよりも、このようにBulk INSERTをすると高速にデータの投入が終わります。 一番簡単な 再帰 クエリ では 再帰 クエリを実行していきましょう。 再帰 クエリの詳細なリファレンスはBigQueryの RECURSIVE キーワード を参照してください。 with recursive org_rec as ( select id ,parent, department, title, name, "" as manager # 1 . from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, a.name as manager # 2 . from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, manager from org_rec where id < 15 order by id; このクエリでは、部署名、役職名、社員名に加えて、当該社員のマネージャーが誰なのかを検索します。 再帰 クエリではカラム数を合わせる必要があり、親レコードがない場合には空文字を選択しています。 再帰 クエリにおける上位レコードから社員名を選択しています。これによってマネージャーが誰なのか分かるのです。 この SQL の実行結果を見てみましょう。 部長である ア さんと 一 さんのmanagerとして いろは さんが選択されていますね。 副部長や、課長の皆さんについても、対応するマネージャーが選択されています。 再帰 クエリに配列を導入する 次は 再帰 クエリから得られる 木構造 において、親に向かう経路を分析してみましょう。 それには、 再帰 しながら配列を組み立てていく SQL を発行します。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, array_length(path) as level, # 3. array_to_string(path, ", ") as route # 4. from org_rec where id < 15 order by id; 配列を社員名で初期化します。 上位階層から来た配列に現在の階層における社員名を追加します。 array_length関数を使って配列の長さを得ることで階層における位置を選択しています。 各社員から階層をたどる経路は、array_to_string関数を使って配列を結合すると得られます。 この SQL の実行結果を見てみましょう。 例えば、8行目の キ さんについて選択されたレコードを見てください。 組織図で キ さんから いろは 事業部長までの経路を確認するとこのようになっています。 いろは 事業部長から見て キ さんは ア 部長の部下である ウ 副部長の部下ですね。 つまり、levelカラムが示すように4層目にいて、その経路は「いろは→ア→ウ→キ」となるわけです。 再帰 クエリに導入した配列のオフセット位置を使う 再帰 クエリで 木構造 をたどるには配列を使うと説明しましたが、配列なのでオフセット位置を直接指定できます。 例えば、以下のようなクエリが考えられます。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select path[offset(0)] as bigboss, # 1. count(path[offset(0)]) as members # 2. from org_rec group by bigboss # 3. 各社員の列にある0番目の要素には必ず事業部長が入っています。 レコードとして事業部長が出現する回数を数えています。 事業部長でグルーピングしています。 この SQL の実行結果を見てみましょう。 各事業部長に連なる社員が事業部長本人を含めて何名なのか選択されていますね。 再帰 クエリと条件分岐を組み合わせる 再帰 クエリで作った配列のオフセット位置を使ってマネージャーを選択してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, path[offset(array_length(path)-2)] as boss # 1. from org_rec order by id; オフセット位置として配列の長さから2を引いた値を使っています。 これを実行すると以下のようなエラーになって期待した通りには動作しません。 Array index -1 is out of bounds (underflow) 事業部長はマネージャーがいないので配列の長さが1です。それによってこのようなエラーになる訳です。 ここでは、case句を使って問題に対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[name] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.name]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select department, title, name, (case when array_length(path) < 2 then null # 1. else path[offset(array_length(path)-2)] # 2. end) as manager from org_rec where id < 15 order by id; 配列の長さが2を下回るものについてはnullを選択しています。 配列の長さが2を下回らないものについては、長さから2を引いた値を指定することでマネージャーを選択しています。 この SQL の実行結果を見てみましょう。 最初に説明したクエリと同じ結果が得られていますね。 再帰 クエリと副問い合わせを組み合わせる 次は、 再帰 クエリで作った配列を使って各社員にとっての事業部長を選択してみましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path # 1. from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path # 2. from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select title, name, (select name from `taichi_test.organization` where id = path[offset(0)] # 3. ) as bigboss from org_rec where id < 15 order by id; 配列をidで初期化します。 上位階層から来た配列に現在の階層におけるidを追加します。 各レコードに含まれる配列の0番目は必ず事業部長のidが入っています。つまり、特に条件を指定せずにオフセット位置を指定できます。 この SQL の実行結果を見てみましょう。 bigboss列に いろは 事業部長が入っていますね。事業部長だけは自己参照しており奇妙な状態になっていますが、これに対する対処は皆さん、ご自分でやってみてください。 説明のためにSELECT句の中で副問い合わせをしましたが、あまり褒められたものではありません。こういう時は本来INNER JOINを使って情報を付加しましょう。 with recursive org_rec as ( select id ,parent, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select a.title, a.name, b.name as bigboss from org_rec as a inner join `taichi_test.organization` as b on a.path[offset(0)] = b.id where a.id < 15 order by a.id; 再帰 クエリに導入した配列をソースに副問い合わせする 最後は、もう少し複雑なクエリを実行してみましょう。 所で、最初のCREATE TABLE文の説明に奇妙な部分があったのはお気づきでしょうか? departmentカラムに入るデータが、事業部長を表すレコードの時だけ事業部名が入っています。 本来的には単一のカラムに複数の意味合いを持たせることは望ましくないのですが、ついうっかりデータを圧縮するためにやってしまいました。 私の手抜きによるやらかしに、データの洗い替えをせずに SQL だけで対処してみましょう。 with recursive org_rec as ( select id ,parent, department, title, name, array[id] as path from `taichi_test.organization` where parent is null union all select b.id, b.parent, b.department, b.title, b.name, array_concat(a.path, [b.id]) as path from org_rec a,`taichi_test.organization` b where a.id = b.parent ) select (select department from `taichi_test.organization` inner join ( select level from unnest(path) as level with offset where offset = 0 # 1. ) as levels on id = levels.level) as unit, department, title, name from org_rec order by id; BigQueryではunnest関数を使うと配列をテーブルであるかのように扱えます。また、併せてwith offset句を使うとクエリの中で配列のオフセット位置を参照できます。 この SQL の実行結果を見てみましょう。 各レコードに事業部名が選択できていますね。 まとめ 今回エントリでは、BigQueryにおける 再帰 クエリの使い方について詳しく説明しました。 再帰 クエリを使う上で配列と組み合わせることはある種のイディオムなのですが、あまり知られていません。 これを知っているだけで、データ分析の幅は確実に広がりますので是非使いこなしてください。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Route 53 Resolver DNS Firewall を使ってみた話です。 VPC のセキュリティグループや AWS WAF と比較すると話題になることが少ないサービスですが、簡単に導入でき、多層防御の手段の一つとして有効であると感じたため、使ってみた際に考えた現実的な設定方法を書き残したいと思います。 Route 53 Resolver とは Route 53 Resolver DNS Firewall とは フェールクローズとフェールオープン Route 53 Resolver DNS Firewall で防げるもの、防げないもの 準備:Resolver Query Log の出力 準備:サブスクリプションフィルターで Alert / Block ログを通知する 基本編:拒否リスト方式で DNS Firewall を作成 応用編:許可リスト方式で DNS Firewall を作成 AWSサービスなどへの内部的な通信 DNS クエリで得られる CNAME デフォルトAlertがおすすめ まとめ Route 53 Resolver とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver は VPC 内から名前解決を担当する DNS リ ゾル バーであり、以前は Amazon Provided DNS と呼ばれていたようです。 VPC のネットワークアドレスに2をプラスした IPアドレス からアクセスできます。( VPC の IPアドレス レンジが 10.0.0.0/16 であれば 10.0.0.2) 例えば、 VPC 内にあるEC2 インスタンス が some-domain.com への名前解決を行う際には、この Route 53 Resolver に問い合わせを行い、結果の IPアドレス によって通信がルートテーブルで制御されます。(内部的には、Route 53 Resolverから他のネームサーバーへ 再帰 的にルックアップが行われます) インターネットからアクセスできるパブリック DNS ホスト名だけではなく、プライベートホストゾーンや VPC 内のプライベート DNS ホスト名の名前解決も担当します。例えばEC2 インスタンス にプライベート DNS 名「ip-10-0-0-11.ap-northeast-1.compute.internal」が付与されている場合、プライベート IPアドレス 「10.0.0.11」への解決もRoute 53 Resolverが担当します。 Route 53 Resolver DNS Firewall とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver-dns-firewall-overview.html DNS Firewall は、Route 53 Resolver による特定 ドメイン への名前解決結果をブロックしたり、オーバーライドしたりできる DNS / UDP レイヤーの ファイアウォール です。以下の構成要素を設定することで使用を開始できます。 ドメインリスト DNS Firewall で評価する対象 ドメイン のリスト 各 ドメイン の先頭に ワイルドカード 「 * 」を使用できる 独自で作成できるほか、 AWSマネージドのドメインリスト が用意されており、既知の マルウェア に関連する ドメイン と ボットネット のC&Cサーバの ドメイン を簡単にブロックできる ルール 名前、説明、 ドメイン リスト、アクション、優先度の組み合わせ アクション は Allow 、 Alert 、 Block の3つが存在する。 Allow は名前解決の許可、 Alert は許可するがログにはアラートとして出力、 Block は名前解決を拒否する Block の場合、さらにレスポンスとしてどのように振る舞うかを定義する。 NODATA 、 NXDOMAIN 、 OVERRIDE の3つから選択する ルールグループ 複数のルールの集合であり、リージョン固定である 優先度の数字が小さいルールから順に評価され、ルールの ドメイン リストに該当する DNS クエリであればそこで評価が終了 いずれのルールにも該当しないクエリは、名前解決が許可される ルールグループとVPCの関連付け ルールグループと VPC を関連付けることで DNS Firewall は有効になる フェールクローズとフェールオープン VPC で DNS Firewall を有効にする場合、 DNS Firewall自身が障害などにより応答しない時のこと も考えておくと良いでしょう。デフォルトではフェールクローズと呼ばれ、 DNS Firewall が応答しない時は名前解決が失敗するようになっています。フェールオープンに変更することでセキュリティよりも可用性が優先され、 DNS Firewall が応答しない時は名前解決が成功するようにできます。 Route 53 Resolver DNS Firewall で防げるもの、防げないもの DNS Firewall は DNS レイヤーの ファイアウォール なので、特定 ドメイン への名前解決をブロックし応答を返さないことで、結果的に対象サーバへの通信を防ぎます。例えば インスタンス が侵入されたり マルウェア に感染した際、悪意のあるサーバとの DNS 名を介した通信をブロックすることで被害の拡大防止に役立ちます。 一方で ドメイン 名ではなく、直接 IPアドレス によるアクセスはRoute 53 Resolverに名前解決を要求しないため、 DNS Firewall によりブロックされません。そのため DNS Firewall による防御は攻撃への根本対策ではなくあくまでも軽減策と考え、常に他の防御手段と組み合わせることを考えるようにしましょう。 準備:Resolver Query Log の出力 ここからは、実際にどのように DNS Firewall を使っていくのが良いのかを考えてみます。 まずは準備として、 Route 53 Resolverのクエリログ を出力するように設定します。これにより、ルールのアクションが Alert や Block の時にどんなクエリが問題となったのかを見つけられるようになります。(ログはクエリが許可された場合も出力されます) CDKで作成する場合は次のようになります。ログの出力先としてCloudWatch Logsロググループ、S3 バケット 、 Kinesis Data Firehoseから選べますが、この例ではCloudWatch Logsロググループとしました。BlockやAlertログの通知を サブスクリプション フィルターで簡単に実装できるためです(後述)。 import * as logs from "aws-cdk-lib/aws-logs" ; import * as route53resolver from "aws-cdk-lib/aws-route53resolver" ; ... // CloudWatch Logsロググループを作成 const queryLogGroup = new logs.LogGroup ( this , "MyResolverQueryLogGroup" , { logGroupName: "my-resolver-query-log-group" , retention: logs.RetentionDays.ONE_MONTH , } ); // Route 53 Resolverログの設定 const queryLogConfig = new route53resolver.CfnResolverQueryLoggingConfig ( this , "MyResolverQueryLoggingConfig" , { name: "cloudwatch logs" , destinationArn: queryLogGroup.logGroupArn , } ); // Route 53 ResolverログをVPCに関連付ける new route53resolver.CfnResolverQueryLoggingConfigAssociation ( this , "MyResolverQueryLoggingConfigAssociation" , { resolverQueryLogConfigId: queryLogConfig.attrId , resourceId: vpc.vpcId , } ); これにより、設定した VPC 内で Route 53 Resolver へ DNS クエリが発生する毎にログに記録されるようになります。下の図では、ログ出力のための logs.ap-northeast-1.amazonaws.com. への名前解決が記録されています。 クエリが Alert 対象となっている場合は、次のように firewall_rule_action 、 firewall_rule_group_id 、 firewall_domain_list_id の3つのフィールドがログエントリーに追加されます。 ( Block の場合は firewall_rule_action が BLOCK になります) 準備: サブスクリプション フィルターで Alert / Block ログを通知する DNS クエリが ALERT や BLOCK 対象となった場合、ログからそれを検知して通知する仕組みを作っておくと、 DNS Firewall の設定の不足を見つけたり、攻撃に素早く反応できるようになるのでおすすめです。CloudWatch Logsから特定のログエントリーを簡単にフィルタリングする方法には、 メトリクスフィルター や サブスクリプションフィルター がありますが、ログエントリー本文(どの DNS クエリが ALERT / BLOCK されたのか)を通知に含めたいため、 サブスクリプション フィルターで実装することにします。 CDKでの実装サンプルは以下のとおりです。 import * as kms from "aws-cdk-lib/aws-kms" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as destinations from "aws-cdk-lib/aws-logs-destinations" ; import * as sns from "aws-cdk-lib/aws-sns" ; import * as subscription from "aws-cdk-lib/aws-sns-subscriptions" ; ... // SNSトピックの作成。(任意)KMSのデフォルトキーで暗号化 const defaultKey = kms.Key.fromLookup ( this , "DefaultSNSKey" , { aliasName: "alias/aws/sns" } ); const topic = new sns.Topic ( this , "MyDnsFirewallNotificationTopic" , { masterKey: defaultKey } ); // 通知送信先のEメールアドレスをサブスクライブ const sub = new subscription.EmailSubscription ( "my-name@my-domain.com" ); topic.addSubscription ( sub ); // サブスクリプションフィルターから受け取ったデータをSNSトピックに送信するLambda関数 const firewallLogFunction = new lambdaNodejs.NodejsFunction ( this , "MyDnsFirewallLogNotificationFunction" , { entry: "functions/dns-firewall-log.ts" , runtime: Runtime.NODEJS_16_X , environment: { TOPIC_ARN: topic.topicArn , } , logRetention: logs.RetentionDays.ONE_MONTH , description: "Subscribe to cloudwatch logs and publish to SNS topic" , } ); topic.grantPublish ( firewallLogFunction ); // CloudWatch Logsサブスクリプションフィルター // firewall_rule_action フィールドが ALERT もしくは BLOCK の場合に // Lambda関数に送信する queryLogGroup.addSubscriptionFilter ( "MyDnsFirewallLogFilter" , { destination: new destinations.LambdaDestination ( firewallLogFunction ), filterPattern: logs.FilterPattern.literal ( '{ $.firewall_rule_action = "ALERT" || $.firewall_rule_action = "BLOCK" }' ), } ); functions/dns-firewall-log.ts に以下のようにLambda関数を作成します。 import * as zlib from "zlib" ; import { SNSClient , PublishCommand } from "@aws-sdk/client-sns" ; import { Handler , CloudWatchLogsEvent , CloudWatchLogsDecodedData } from "aws-lambda" ; const client = new SNSClient ( { region: process .env.AWS_REGION } ); export const handler: Handler = async ( input: CloudWatchLogsEvent ) => { // ペイロードからログエントリーを取り出す // https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample const payload = Buffer . from( input.awslogs.data , "base64" ); const result = await new Promise < string >(( res , rej ) => { zlib.gunzip ( payload , ( e , result ) => { return e ? rej ( e ) : res ( result.toString ( "ascii" )); } ); } ); const parsedResult = JSON .parse ( result ) as CloudWatchLogsDecodedData ; const message = parsedResult.logEvents.map (( event ) => event.message ) .join ( "\n\n" ); console .log ( "Event Data:" , message ); // SNSトピックに送信 await client.send ( new PublishCommand ( { TopicArn: process .env.TOPIC_ARN , Subject: "DNS Firewall Log Notification" , Message: message , } ) ); } ; 基本編:拒否リスト方式で DNS Firewall を作成 いよいよ DNS Firewall 自体の設定をしていきます。一番簡単なのは、用意されている AWSマネージドのドメインリスト を利用することです。これらの ドメイン リストの名前解決を拒否するだけで、一定の防御効果があります。 AWSManagedDomainsBotnetCommandandControl と AWSManagedDomainsMalwareDomainList の2つの ドメイン リストが用意されているので、これらを BLOCK し、それ以外の ドメイン は全て許可する拒否リスト方式が簡単に実装できます。 コンソールでの設定方法は 公式ドキュメント を参考にすれば良いので、CDKでの実装例を掲載します。なお、 AWS マネージドの ドメイン リストIDはリージョン毎に決まっており、コンソールから確認したものを利用しました。以下のサンプルは東京リージョンのIDを使用しています。 // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストをBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); 応用編:許可リスト方式で DNS Firewall を作成 より厳しく、名前解決する ドメイン を明示的に指定する許可リスト方式にすることもできます。しかし許可する ドメイン の一覧に漏れがあると、アプリケーションの通信に思わぬ不具合が発生する可能性があるため、慎重に設定しなければなりません。 すなわち VPC 内からの全ての正当な名前解決を明示的に許可する必要があり、これには以下のような通信が含まれます。 インターネット上の公開 ドメイン への通信 利用しているプライベートホストゾーンの ドメイン への通信 AWS サービスなどへの内部的な通信 DNS クエリで得られる CNAME 上の2つはアプリケーションから明示的に通信先を指定する場合が多いので比較的把握しやすいと思いますが、下の2つがなかなか厄介です。 AWS サービスなどへの内部的な通信 EC2 インスタンス を立ち上げただけで、次のように様々な ドメイン への名前解決要求をしていることがログからわかりました。通信先と用途を全て把握するのは難しそうです。 135.7.0.10.in-addr.arpa. does-not-exist.example.com. __cloud_init_expected_not_found__. instance-data. __cloud_init_expected_not_found__.ap-northeast-1.compute.internal. example.invalid. s3.dualstack.ap-northeast-1.amazonaws.com. amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com. 1.amazon.pool.ntp.org. 0.amazon.pool.ntp.org. 2.amazon.pool.ntp.org. DNS クエリで得られる CNAME 例えば tech.isid.co.jp. への dig コマンドの結果は次の通りであり、 hatenablog.com. の別名となっていることがわかります。 % dig tech.isid.co.jp ;; ANSWER SECTION: tech.isid.co.jp. 86400 IN CNAME hatenablog.com. hatenablog.com. 60 IN A 35.75.255.9 hatenablog.com. 60 IN A 54.199.90.60 これを名前解決する際には、まずは tech.isid.co.jp. への DNS クエリが発生し、その後 hatenablog.com. へのクエリが発生します。両方に対して DNS Firewall のルールが評価されるため、通信を許可するには全ての CNAME に対する名前解決を許可する必要があります。特に自組織で管理している ドメイン でない場合は突然CNAMEが変わる可能性もあります。 デフォルトAlertがおすすめ 以上により、名前解決を許可したい ドメイン を完全に把握するのは難しいケースが多いと思いますので、許可リスト方式においてはデフォルトの挙動を Block ではなく Alert にするのがおすすめです。 Alert ログを通知するように設定しているため、通知を受けたら必要な通信かどうかを都度判断し、許可する ドメイン リストに追加していく運用が良さそうです。 とはいえ AWS マネージドの ドメイン リストは明示的にブロックしたいので、これらを組み合わせたルールグループのCDK実装例は次のようになります。 // 許可するドメインのリストを定義 const allowedDomainList = new route53resolver.CfnFirewallDomainList ( this , "MyDnsFirewallAllowedDomains" , { name: "My Dns Firewall allowed list" , domains: [ "*.compute.internal." , "*.amazonaws.com." , "*.ec2.internal." , "*.compute-1.internal." , "my-domain.com." ] , } ); // デフォルトAlert用の全ドメイン const allDomains = new route53resolver.CfnFirewallDomainList ( this , "DnsFirewallAllDomains" , { name: "All domains" , domains: [ "*" ] , } ); // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストは先にBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , // 許可するドメインリスト { action: "ALLOW" , priority: 10 , firewallDomainListId: allowedDomainList.attrId , } , // それ以外の全ドメインはAlertとする { action: "ALERT" , priority: 1000 , firewallDomainListId: allDomains.attrId , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); まとめ まとめると、Route 53 Resolver DNS Firewall の現実的な設定は次のようになります。 DNS Firewall はデフォルトでフェールクローズなので、セキュリティよりも可用性を優先したい場合はフェールオープンに変更しよう Route 53 Resolver Query Logを出力しよう Query Log の Alert と Block を通知しよう AWS マネージドの ドメイン リストを利用して悪意のあるサーバとの通信を簡単にブロックしよう より厳しく設定したい場合、許可リスト方式でデフォルト Alert のルールグループを作成しよう なお、 DNS Firewall とQuery Logの保存先には料金が発生しますので、設定時にはご留意ください。 https://aws.amazon.com/jp/route53/pricing/ お読みいただきありがとうございました。 (2022/12/21追記) いつの間にか AWS 管理の ドメイン リストに AWSManagedDomainsAggregateThreatList が追加されていました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 Route 53 Resolver DNS Firewall を使ってみた話です。 VPC のセキュリティグループや AWS WAF と比較すると話題になることが少ないサービスですが、簡単に導入でき、多層防御の手段の一つとして有効であると感じたため、使ってみた際に考えた現実的な設定方法を書き残したいと思います。 Route 53 Resolver とは Route 53 Resolver DNS Firewall とは フェールクローズとフェールオープン Route 53 Resolver DNS Firewall で防げるもの、防げないもの 準備:Resolver Query Log の出力 準備:サブスクリプションフィルターで Alert / Block ログを通知する 基本編:拒否リスト方式で DNS Firewall を作成 応用編:許可リスト方式で DNS Firewall を作成 AWSサービスなどへの内部的な通信 DNS クエリで得られる CNAME デフォルトAlertがおすすめ まとめ Route 53 Resolver とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver.html Route 53 Resolver は VPC 内から名前解決を担当する DNS リ ゾル バーであり、以前は Amazon Provided DNS と呼ばれていたようです。 VPC のネットワークアドレスに2をプラスした IPアドレス からアクセスできます。( VPC の IPアドレス レンジが 10.0.0.0/16 であれば 10.0.0.2) 例えば、 VPC 内にあるEC2 インスタンス が some-domain.com への名前解決を行う際には、この Route 53 Resolver に問い合わせを行い、結果の IPアドレス によって通信がルートテーブルで制御されます。(内部的には、Route 53 Resolverから他のネームサーバーへ 再帰 的にルックアップが行われます) インターネットからアクセスできるパブリック DNS ホスト名だけではなく、プライベートホストゾーンや VPC 内のプライベート DNS ホスト名の名前解決も担当します。例えばEC2 インスタンス にプライベート DNS 名「ip-10-0-0-11.ap-northeast-1.compute.internal」が付与されている場合、プライベート IPアドレス 「10.0.0.11」への解決もRoute 53 Resolverが担当します。 Route 53 Resolver DNS Firewall とは 公式ドキュメント: https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resolver-dns-firewall-overview.html DNS Firewall は、Route 53 Resolver による特定 ドメイン への名前解決結果をブロックしたり、オーバーライドしたりできる DNS / UDP レイヤーの ファイアウォール です。以下の構成要素を設定することで使用を開始できます。 ドメインリスト DNS Firewall で評価する対象 ドメイン のリスト 各 ドメイン の先頭に ワイルドカード 「 * 」を使用できる 独自で作成できるほか、 AWSマネージドのドメインリスト が用意されており、既知の マルウェア に関連する ドメイン と ボットネット のC&Cサーバの ドメイン を簡単にブロックできる ルール 名前、説明、 ドメイン リスト、アクション、優先度の組み合わせ アクション は Allow 、 Alert 、 Block の3つが存在する。 Allow は名前解決の許可、 Alert は許可するがログにはアラートとして出力、 Block は名前解決を拒否する Block の場合、さらにレスポンスとしてどのように振る舞うかを定義する。 NODATA 、 NXDOMAIN 、 OVERRIDE の3つから選択する ルールグループ 複数のルールの集合であり、リージョン固定である 優先度の数字が小さいルールから順に評価され、ルールの ドメイン リストに該当する DNS クエリであればそこで評価が終了 いずれのルールにも該当しないクエリは、名前解決が許可される ルールグループとVPCの関連付け ルールグループと VPC を関連付けることで DNS Firewall は有効になる フェールクローズとフェールオープン VPC で DNS Firewall を有効にする場合、 DNS Firewall自身が障害などにより応答しない時のこと も考えておくと良いでしょう。デフォルトではフェールクローズと呼ばれ、 DNS Firewall が応答しない時は名前解決が失敗するようになっています。フェールオープンに変更することでセキュリティよりも可用性が優先され、 DNS Firewall が応答しない時は名前解決が成功するようにできます。 Route 53 Resolver DNS Firewall で防げるもの、防げないもの DNS Firewall は DNS レイヤーの ファイアウォール なので、特定 ドメイン への名前解決をブロックし応答を返さないことで、結果的に対象サーバへの通信を防ぎます。例えば インスタンス が侵入されたり マルウェア に感染した際、悪意のあるサーバとの DNS 名を介した通信をブロックすることで被害の拡大防止に役立ちます。 一方で ドメイン 名ではなく、直接 IPアドレス によるアクセスはRoute 53 Resolverに名前解決を要求しないため、 DNS Firewall によりブロックされません。そのため DNS Firewall による防御は攻撃への根本対策ではなくあくまでも軽減策と考え、常に他の防御手段と組み合わせることを考えるようにしましょう。 準備:Resolver Query Log の出力 ここからは、実際にどのように DNS Firewall を使っていくのが良いのかを考えてみます。 まずは準備として、 Route 53 Resolverのクエリログ を出力するように設定します。これにより、ルールのアクションが Alert や Block の時にどんなクエリが問題となったのかを見つけられるようになります。(ログはクエリが許可された場合も出力されます) CDKで作成する場合は次のようになります。ログの出力先としてCloudWatch Logsロググループ、S3 バケット 、 Kinesis Data Firehoseから選べますが、この例ではCloudWatch Logsロググループとしました。BlockやAlertログの通知を サブスクリプション フィルターで簡単に実装できるためです(後述)。 import * as logs from "aws-cdk-lib/aws-logs" ; import * as route53resolver from "aws-cdk-lib/aws-route53resolver" ; ... // CloudWatch Logsロググループを作成 const queryLogGroup = new logs.LogGroup ( this , "MyResolverQueryLogGroup" , { logGroupName: "my-resolver-query-log-group" , retention: logs.RetentionDays.ONE_MONTH , } ); // Route 53 Resolverログの設定 const queryLogConfig = new route53resolver.CfnResolverQueryLoggingConfig ( this , "MyResolverQueryLoggingConfig" , { name: "cloudwatch logs" , destinationArn: queryLogGroup.logGroupArn , } ); // Route 53 ResolverログをVPCに関連付ける new route53resolver.CfnResolverQueryLoggingConfigAssociation ( this , "MyResolverQueryLoggingConfigAssociation" , { resolverQueryLogConfigId: queryLogConfig.attrId , resourceId: vpc.vpcId , } ); これにより、設定した VPC 内で Route 53 Resolver へ DNS クエリが発生する毎にログに記録されるようになります。下の図では、ログ出力のための logs.ap-northeast-1.amazonaws.com. への名前解決が記録されています。 クエリが Alert 対象となっている場合は、次のように firewall_rule_action 、 firewall_rule_group_id 、 firewall_domain_list_id の3つのフィールドがログエントリーに追加されます。 ( Block の場合は firewall_rule_action が BLOCK になります) 準備: サブスクリプション フィルターで Alert / Block ログを通知する DNS クエリが ALERT や BLOCK 対象となった場合、ログからそれを検知して通知する仕組みを作っておくと、 DNS Firewall の設定の不足を見つけたり、攻撃に素早く反応できるようになるのでおすすめです。CloudWatch Logsから特定のログエントリーを簡単にフィルタリングする方法には、 メトリクスフィルター や サブスクリプションフィルター がありますが、ログエントリー本文(どの DNS クエリが ALERT / BLOCK されたのか)を通知に含めたいため、 サブスクリプション フィルターで実装することにします。 CDKでの実装サンプルは以下のとおりです。 import * as kms from "aws-cdk-lib/aws-kms" ; import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs" ; import * as destinations from "aws-cdk-lib/aws-logs-destinations" ; import * as sns from "aws-cdk-lib/aws-sns" ; import * as subscription from "aws-cdk-lib/aws-sns-subscriptions" ; ... // SNSトピックの作成。(任意)KMSのデフォルトキーで暗号化 const defaultKey = kms.Key.fromLookup ( this , "DefaultSNSKey" , { aliasName: "alias/aws/sns" } ); const topic = new sns.Topic ( this , "MyDnsFirewallNotificationTopic" , { masterKey: defaultKey } ); // 通知送信先のEメールアドレスをサブスクライブ const sub = new subscription.EmailSubscription ( "my-name@my-domain.com" ); topic.addSubscription ( sub ); // サブスクリプションフィルターから受け取ったデータをSNSトピックに送信するLambda関数 const firewallLogFunction = new lambdaNodejs.NodejsFunction ( this , "MyDnsFirewallLogNotificationFunction" , { entry: "functions/dns-firewall-log.ts" , runtime: Runtime.NODEJS_16_X , environment: { TOPIC_ARN: topic.topicArn , } , logRetention: logs.RetentionDays.ONE_MONTH , description: "Subscribe to cloudwatch logs and publish to SNS topic" , } ); topic.grantPublish ( firewallLogFunction ); // CloudWatch Logsサブスクリプションフィルター // firewall_rule_action フィールドが ALERT もしくは BLOCK の場合に // Lambda関数に送信する queryLogGroup.addSubscriptionFilter ( "MyDnsFirewallLogFilter" , { destination: new destinations.LambdaDestination ( firewallLogFunction ), filterPattern: logs.FilterPattern.literal ( '{ $.firewall_rule_action = "ALERT" || $.firewall_rule_action = "BLOCK" }' ), } ); functions/dns-firewall-log.ts に以下のようにLambda関数を作成します。 import * as zlib from "zlib" ; import { SNSClient , PublishCommand } from "@aws-sdk/client-sns" ; import { Handler , CloudWatchLogsEvent , CloudWatchLogsDecodedData } from "aws-lambda" ; const client = new SNSClient ( { region: process .env.AWS_REGION } ); export const handler: Handler = async ( input: CloudWatchLogsEvent ) => { // ペイロードからログエントリーを取り出す // https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample const payload = Buffer . from( input.awslogs.data , "base64" ); const result = await new Promise < string >(( res , rej ) => { zlib.gunzip ( payload , ( e , result ) => { return e ? rej ( e ) : res ( result.toString ( "ascii" )); } ); } ); const parsedResult = JSON .parse ( result ) as CloudWatchLogsDecodedData ; const message = parsedResult.logEvents.map (( event ) => event.message ) .join ( "\n\n" ); console .log ( "Event Data:" , message ); // SNSトピックに送信 await client.send ( new PublishCommand ( { TopicArn: process .env.TOPIC_ARN , Subject: "DNS Firewall Log Notification" , Message: message , } ) ); } ; 基本編:拒否リスト方式で DNS Firewall を作成 いよいよ DNS Firewall 自体の設定をしていきます。一番簡単なのは、用意されている AWSマネージドのドメインリスト を利用することです。これらの ドメイン リストの名前解決を拒否するだけで、一定の防御効果があります。 AWSManagedDomainsBotnetCommandandControl と AWSManagedDomainsMalwareDomainList の2つの ドメイン リストが用意されているので、これらを BLOCK し、それ以外の ドメイン は全て許可する拒否リスト方式が簡単に実装できます。 コンソールでの設定方法は 公式ドキュメント を参考にすれば良いので、CDKでの実装例を掲載します。なお、 AWS マネージドの ドメイン リストIDはリージョン毎に決まっており、コンソールから確認したものを利用しました。以下のサンプルは東京リージョンのIDを使用しています。 // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストをBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); 応用編:許可リスト方式で DNS Firewall を作成 より厳しく、名前解決する ドメイン を明示的に指定する許可リスト方式にすることもできます。しかし許可する ドメイン の一覧に漏れがあると、アプリケーションの通信に思わぬ不具合が発生する可能性があるため、慎重に設定しなければなりません。 すなわち VPC 内からの全ての正当な名前解決を明示的に許可する必要があり、これには以下のような通信が含まれます。 インターネット上の公開 ドメイン への通信 利用しているプライベートホストゾーンの ドメイン への通信 AWS サービスなどへの内部的な通信 DNS クエリで得られる CNAME 上の2つはアプリケーションから明示的に通信先を指定する場合が多いので比較的把握しやすいと思いますが、下の2つがなかなか厄介です。 (2024/5/8追記) DNS Firewall において ドメインのリダイレクトがサポート され、設定を行うことでCNAMEを明示的に許可する必要がなくなりました。 AWS サービスなどへの内部的な通信 EC2 インスタンス を立ち上げただけで、次のように様々な ドメイン への名前解決要求をしていることがログからわかりました。通信先と用途を全て把握するのは難しそうです。 135.7.0.10.in-addr.arpa. does-not-exist.example.com. __cloud_init_expected_not_found__. instance-data. __cloud_init_expected_not_found__.ap-northeast-1.compute.internal. example.invalid. s3.dualstack.ap-northeast-1.amazonaws.com. amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com. 1.amazon.pool.ntp.org. 0.amazon.pool.ntp.org. 2.amazon.pool.ntp.org. DNS クエリで得られる CNAME 例えば tech.isid.co.jp. への dig コマンドの結果は次の通りであり、 hatenablog.com. の別名となっていることがわかります。 % dig tech.isid.co.jp ;; ANSWER SECTION: tech.isid.co.jp. 86400 IN CNAME hatenablog.com. hatenablog.com. 60 IN A 35.75.255.9 hatenablog.com. 60 IN A 54.199.90.60 これを名前解決する際には、まずは tech.isid.co.jp. への DNS クエリが発生し、その後 hatenablog.com. へのクエリが発生します。両方に対して DNS Firewall のルールが評価されるため、通信を許可するには全ての CNAME に対する名前解決を許可する必要があります。特に自組織で管理している ドメイン でない場合は突然CNAMEが変わる可能性もあります。 (2024/5/8追記) DNS Firewall において ドメインのリダイレクトがサポート され、設定を行うことでCNAMEを明示的に許可する必要がなくなりました。 デフォルトAlertがおすすめ 以上により、名前解決を許可したい ドメイン を完全に把握するのは難しいケースが多いと思いますので、許可リスト方式においてはデフォルトの挙動を Block ではなく Alert にするのがおすすめです。 Alert ログを通知するように設定しているため、通知を受けたら必要な通信かどうかを都度判断し、許可する ドメイン リストに追加していく運用が良さそうです。 とはいえ AWS マネージドの ドメイン リストは明示的にブロックしたいので、これらを組み合わせたルールグループのCDK実装例は次のようになります。 // 許可するドメインのリストを定義 const allowedDomainList = new route53resolver.CfnFirewallDomainList ( this , "MyDnsFirewallAllowedDomains" , { name: "My Dns Firewall allowed list" , domains: [ "*.compute.internal." , "*.amazonaws.com." , "*.ec2.internal." , "*.compute-1.internal." , "my-domain.com." ] , } ); // デフォルトAlert用の全ドメイン const allDomains = new route53resolver.CfnFirewallDomainList ( this , "DnsFirewallAllDomains" , { name: "All domains" , domains: [ "*" ] , } ); // ルールグループ作成 const dnsFirewallRuleGroup = new route53resolver.CfnFirewallRuleGroup ( this , "MyDnsFirewallRuleGroup" , { name: "My DNS Firewall rule group" , firewallRules: [ // AWS管理のドメインリストは先にBlock { action: "BLOCK" , priority: 1 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-1a63d8549cca46e6" , } , { action: "BLOCK" , priority: 2 , blockResponse: "NODATA" , firewallDomainListId: "rslvr-fdl-dc19e97bef3c454a" , } , // 許可するドメインリスト { action: "ALLOW" , priority: 10 , firewallDomainListId: allowedDomainList.attrId , } , // それ以外の全ドメインはAlertとする { action: "ALERT" , priority: 1000 , firewallDomainListId: allDomains.attrId , } , ] , } ); // ルールグループをVPCに関連付け // priorityは1000以上を指定する new route53resolver.CfnFirewallRuleGroupAssociation ( this , "MyDnsFirewallRuleGroupAssociation" , { name: "My DNS Firewall rule group association" , priority: 1000 , firewallRuleGroupId: dnsFirewallRuleGroup.attrId , vpcId: vpc.vpcId , } ); まとめ まとめると、Route 53 Resolver DNS Firewall の現実的な設定は次のようになります。 DNS Firewall はデフォルトでフェールクローズなので、セキュリティよりも可用性を優先したい場合はフェールオープンに変更しよう Route 53 Resolver Query Logを出力しよう Query Log の Alert と Block を通知しよう AWS マネージドの ドメイン リストを利用して悪意のあるサーバとの通信を簡単にブロックしよう より厳しく設定したい場合、許可リスト方式でデフォルト Alert のルールグループを作成しよう なお、 DNS Firewall とQuery Logの保存先には料金が発生しますので、設定時にはご留意ください。 https://aws.amazon.com/jp/route53/pricing/ お読みいただきありがとうございました。 (2022/12/21追記) いつの間にか AWS 管理の ドメイン リストに AWSManagedDomainsAggregateThreatList が追加されていました。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア(セキュリティ設計) 執筆: @kou.kinyo 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Web3開発入門シリーズ with Algorand(アルゴランド)、今回のテーマは、 アセットの作成 です。 今回の記事のGoogle Colab用のノートブックはこちらになります。ブラウザだけでブロックチェーンにアクセスするコードを、実際に動かしながら試せるので、おすすめです。 Ethereum などの ブロックチェーン では、貨幣的な価値を持つ トーク ンである Fungible Token (FT)や代替不可能な トーク ンである Non Fungible Token (NFT)が存在します。 Algorand では、 FT と NFT を統合したものをアセットと呼んでいます。 もう一つ理解しておきたいことは、 Algorand のアセットは、 Ethereum のようにスマート コントラ クトではないということです。 アセットに対する基本的な操作は、 Algoland の機能として実装されているので、個別のアセットで実装する必要がないのです。 もちろん、アセット固有のロジックがある場合は、スマート コントラ クトで個別に実装することもできます。 Web3開発入門 with Algorandの全てのコンテンツ アカウント TestNetでALGOをゲット 支払い アセットの作成 py-algorand-sdkのインストール アカウントの作成 アカウントにAlgorand Dispenserを使って入金する AlgodClientオブジェクトの作成 AssetConfigTxnオブジェクトの作成 署名 トランザクションをAlgorandに送る トランザクションが確定するのを待つ アセットの確認 仲間募集 これから、アセットを作成します。 py-algorand- sdk のインストール py-algorand-sdk をインストールします。 下記のコードを実行してください。 !pip3 install py-algorand-sdk アカウントの作成 アセットを作成するアカウントを作成しましょう。 下記のコードを実行してください。 from algosdk import account private_key, address = account.generate_account() print("Account Address:", address) 出力結果の例です。 Account Address: H7BCYZGX4J6QKXEG5TYMMVQVSTRHWL4WWQQVBGH725HHIYL2R72RDSMNN4 アカウントにAlgorand Dispenserを使って入金する アセットを作成するためには、 トランザクション 手数料を払う必要があります。 TestNet では、 Algorand Dispenser のサイトで、 ALGO を無料でゲットできます。 Account Address: の後ろの部分のアドレスをコピーしてください。 Algorand Dispenser のサイトにアクセスし、 I'm not a roboot をチェックし、 target address の入力エリアに先ほどコピーしたアドレスをペーストします。 Dispense のボタンをクリックしてください。 Status: Code 200 success のように表示されればOKです。 AlgodClientオブジェクトの作成 Algorand にアクセスするために、 AlgodClient オブジェクトを作成します。 下記のコードを実行してください。 from algosdk.v2client.algod import AlgodClient algod = AlgodClient("", "https://node.testnet.algoexplorerapi.io:443") AlgodClient オブジェクトを作成するときに、 https://node.testnet.algoexplorerapi.io:443 を指定しています。これにより Algoexplorer の TestNet のノードに接続します。 AssetConfigTxnオブジェクトの作成 アセットを作成するためには、 AssetConfigTxn オブジェクトを作成します。 AssetConfigTxn オブジェクトを作成するときのパラメータを説明します。 asset_name は、アセットの名前です。32バイト以内である必要があります。 unit_name は、アセットの単位名です。8バイト以内である必要があります。 total は、どれだけアセットを作成するか指定します。例えば、 1000000 を指定した場合、百万個のアセットを作成することになります。 decimals は小数点以下の桁数です。例えば、3を指定した場合、このアセットの最小の単位は、 0.001 になります。 ややこしいのは、 total が decimals の影響を受けることです。 total が 1000000 で decimals が3の場合、市場に流通するアセットは 1000.000 になります。 decimals が3の場合、アセットの最小単位は、 0.001 なので、それが total で 1000000 個、市場に出回るので、市場で流通するアセットの量は、 0.001 x 1000000 で 1000.000 になるということです。 default_frozen について説明する前に、アセットの凍結(frozen)について説明します。 アカウントAがアセットZを2つ作成し、一つをアカウントBに、もう一つをアカウントCに売ったとします。その後、アカウントBが良くない行動をとっていることが判明したため、アカウントBのアセットZを凍結します。 凍結されたアセットZは、売ることができなくなります。 アセットの凍結はアカウント単位です。アカウントBのアセットZは凍結されているので、売ることはできませんが、アカウントCのアセットZは自由に売ることができます。 default_frozen に True を指定すると、凍結された状態でアセットが作成されます。 manager には、作ったアセットを修正したり、削除したりすることができるアドレスを指定します。 reserve は、ほとんど使われていないので、気にしなくても大丈夫です。 frozen には、作ったアセットを凍結することのできるアドレスを指定します。 clawback には、作ったアセットを自由に移動(Transfer)させることのできるアドレスを指定します。 この clawback は非常に強力で、アカウントAがアカウントCにアセットを売ったあと、アカウントCが持っているアセットを強制的にアカウントAのものにすることもできます。 さらに、強力なことに、凍結されたアセットも自由に動かすことができます。 ただし、 clawback は悪いことをするために用意されているわけではなく、スマート コントラ クトの address を指定することがよく行われます。 特定の条件を満たしたときだけ、スマート コントラ クトがアセットを移動できるようにするためです。 それでは、 AssetConfigTxn オブジェクトを作成しましょう。 下記のコードを実行してください。 from algosdk.future.transaction import AssetConfigTxn sp = algod.suggested_params() txn = AssetConfigTxn( sender=address, sp=sp, asset_name="Test", unit_name="Test", total=1000, decimals=0, default_frozen=False, manager=address, reserve=address, freeze=address, clawback=address, ) 署名 トランザクション は、差出人の秘密キーで署名をする必要があります。 下記のコードを実行してください。 signed_txn = txn.sign(private_key) トランザクション をAlgorandに送る 署名した トランザクション を Algorand に送り出しましょう それを行うのが、 AlgodClientオブジェクト.send_transactions() になります。 下記のコードを実行してください。 tx_id = algod.send_transactions([signed_txn]) print('Transaction ID:', tx_id) トランザクション が確定するのを待つ トランザクション を Algoland に送っても、それで トランザクション が確定されたわけではありません。 トランザクション が確定されるのを待つ必要があります。 トランザクション が確定されるのを待つには、 wait_for_confirmation() を呼び出します。 下記のコードを実行してください。 from algosdk.future.transaction import wait_for_confirmation wait_for_confirmation(algod, tx_id, 10) アセットの確認 Algoexplorer でアセットを確認してみましょう。 アカウントのアドレスをコピーしてください。 TestNetのAlgoexplorer のサイトにアクセスし、一番上の真ん中の検索エリアにコピーして実行してください。 真ん中の方に Transactions と Assets のタブがあるので、 Assets のタブをクリックします。 先ほど作成したアセットの Balance が 1000 になってますね。 なにか感想があれば、 Twitter で@yasuo_algoにメンションしてつぶやいてください。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター