技術書典6 Knative本 読了後感想
実はタイトルの技術書展6には実は行けなかったのですが、 気になっていた本があったので思わずBoothでポチって読んで、手を動かした感想です。
感想を書く対象の本はこちらになります。 『Knativeの歩き方 KubernetesからServerlessを訪ねて』
感想
まず50ページ弱の文字通り薄い本なので、かなり読みやすいです。 構成は最も理解しやすいであろう順序で書かれているので、頭から読んで苦になりません。技術書として最高です。
- 構成
- Knative概要
- Kubernetesクラスタ(GKE)の準備
- Knative Serving
- Knative Build
- Knative Eventing
特に、Serving --> Build --> Build + Serving --> Eventing + Servingの流れは素晴らしい。Serving + Buildのあたりなんか、もう明日には使えそう。
また、プラットフォーム依存の説明が少ないので、他のプラットフォームでも応用させやすい本ですね。
今やk8sはどのプラットフォームもサービスとして提供してるから当然ではあるのだが、GKE特有の説明もさらっと終わらせるので良い。公式にインストール方法が書かれているので、IBM CloudのIKS、AWSのEKS、AzureのAKS、Openshiftでも同様に動かせるはずです。
さらに言うと、説明が丁寧!読者を置いてけぼりにせずスラスラ読めます。
KnativeはCRD(Custom Resource Definition)を使って独自のKubernetesオブジェクトを作成することで様々な機能を拡張してますが、各CRDごとの役割を省かず丁寧に説明されています。独自の用語ばかりだとついていけなくて積み本行きになりがちですが、全くそんなことなかった。
Knativeについて思うこと
正直な話、コンテナ周りって扱うのつらさを感じるんですよね。便利なのは使ってて分かりますけど。 コンテナ単品だとランタイムやアプリをラップしているだけだし、Kubernetesでコンテナオーケストレーションすると今度はクラスタの管理やYAMLの記述に圧殺される。ネットワークやサービスメッシュは別プロダクトのIstioやLinkerdを使わないといけない。
こんなもどかしさを抱えているときに、「開発に集中する」ことを実現できるKnativeはかなり魅力的なプロダクトですね。 コンテナをビルドできる、そのままデプロイしてサービスを公開できる、イベントドリブンな構成も1つのプラットフォームでできてしまうのは驚き。 HTTPベースだけじゃなく、イベントを扱えるところも今までにない。
ただ、build, deploy, and manage modern serverless workloads
と、サーバーレスを自称しているのどういうことだろうと考えこんでしまったのも事実。
なぜなら、今までServerless = FaaSという認識を持っていたから。CNCF Serverless Whitepaper v1.0でもFaaSを重点的に取り上げるくらいですしね。
思うに、文字通りプラットフォーム(インフラ構成やら、クラウドプロバイダーがどれやら、どの言語を使っているやら) = サーバーを意識させないところなんだろうな。もしくはプラットフォームの制約を受けないところ。
「開発に集中する」ことを実現している点では、開発者側から見ると既存のPaaS~FaaSと変わらないです。 CloudFoundryやHerokuで体験したコードを書いてコマンド一発でアプリができるという素晴らしい体験を、コンテナ・Kubernetesでオープンに実現しようというのがKnativeなんじゃないかなとこの本を読みながら考えさせられました。
以上。
Node.jsアプリでもAngularみたいにDIしながらアプリを作る
AngularではDependency Injection(DI)で自分で定義したServiceを各コンポーネントで使えるようにできます。Javaの世界でもSpring Frameworkを筆頭に、インターフェイスに対してどのような実装を紐づけるかをDIで実現しています。
ただ、、、サーバーサイドやCLIでNode.jsのアプリを作るとなると途端にDIをすることって少ないですよね。TypeScriptで型やインターフェイスを手に入れたのにClassをいちいちnewしたりimportするのは辛い...とても辛い!!
この記事ではInversifyJSを使ってサーバーサイドのNode.jsのアプリでDIを行います。
概要
実現したいこと
- Node.jsアプリでDIを使ってInterfaceと実装クラスを疎結合にする。
- 生のJSではなく、TypeScriptを使ってClassとInterfaceの恩恵を受ける。
- 題材はCLIアプリケーションとする。
- InterfaceへのDIだけでなく、実装クラスのDIについてもやってみる。
題材
最近お仕事で自分でやったことを例とします。題材とシチュエーションは以下の通りです。ちょっとした自動化アプリです。
- Java(Spring Framework)のWebAPIを作っています。
- 入力チェックのテストコードを全項目網羅して正しく入力チェックエラーを返すことを確認したい。
- 手で書いて目Grepで過不足がないか確認するのは効率がよくない&何かしら見落とすリスクがあるので自動生成する。
- この自動生成をCLIをインターフェイスとしたアプリケーションとして実装する。
InversifyJS
今回のように、JavaScript/TypeScriptでDIを実現するのに不可欠なライブラリです。
正体はTypeScriptで作られたIoCコンテナ(=DIコンテナ)のライブラリ。TypeScriptで使うことが推奨されています。
また、最近のTypeScriptの流れなのか、アノテーションを使っています。@Inject
や@Injectable
といったJavaでもお馴染みのやつを使えます。
詳細はGitHubのREADME.mdを読んでいただければと。
実践
完成形
最初に最終形をお見せします。 ファイル・ディレクトリは以下の通りになります。
CLIのエントリーポイントがsrc/generator.ts
です。
各ディレクトリはクリーンアーキテクチャもどきで構成しています。各ディレクトリと格納クラスの役割は以下の通りです。
- controller
- usecase
- business
- ビジネスロジックを入れます。
- ここだけInterfaceを定義せず、実装クラスのみ置いてます。
- repository
- データストアへの入出力を行います。
- ファイルを扱うのか、DBを使うのかといった実装部分は呼び出し側には直接関係ないので、呼び出し側は
RepositoryInterface.ts
に定義したインターフェイスにのみ依存するようにします。
- common
- 共通部品を入れます。
- Interfaceと実装クラスを紐づける
inversify.config.ts
もこのディレクトリに突っ込んでます。
Interfaceを定義する
まずはController、UseCase、RepositoryのInterfaceを定義します。全部書いているとキリがないので一部だけ。
import { CreateScriptRequest } from "./CreateScriptRequest"; import { CreateScriptResponse } from "./CreateScriptResponse"; export interface ICreateScriptUseCase { generate(request: CreateScriptRequest): CreateScriptResponse; }
実装は何も入れていません。どのようにこのgenerate
関数が実現されるかは実装クラスに委ねられます。そして使用する側は実装クラスではなく、Interfaceにのみ依存します。
実装クラスを書く
Interfaceを実装します。前述した通り、JavaのInjectアノテーションが出てきますね。
Interfaceに実装クラスを注入する対象は @Inject()
、他のクラスで注入させたい対象は @Injectable()
を使用すればあとはただのアプリです。
import { ICreateScriptUseCase } from "./UseCaseInterface"; import { CreateScriptRequest } from "./CreateScriptRequest"; import { CreateScriptResponse, CreateScriptResponseDetail } from "./CreateScriptResponse"; import { CaseScriptListBlg } from "../business/CaseScriptListBlg"; import { CaseScriptBlg } from "../business/CaseScriptBlg"; import { injectable, inject } from "inversify"; import { TYPE } from "../common/Types"; /** * スクリプト生成ユースケースの実装クラス */ @injectable() export class CreateScriptInteractor implements ICreateScriptUseCase { private readonly _listBlg: CaseScriptListBlg; private readonly _scriptBlg: CaseScriptBlg; constructor( @inject(TYPE.CaseScriptListBlg) listBlg: CaseScriptListBlg, @inject(TYPE.CaseScriptBlg) scriptBlg: CaseScriptBlg ) { this._listBlg = listBlg; this._scriptBlg = scriptBlg; } /** * スクリプト生成を実行する. */ generate(request: CreateScriptRequest): CreateScriptResponse { // テストケースのリストを生成 const caseList = this._listBlg.createCaseList(request); // ケース名を元にテストケーススクリプトを生成 this._scriptBlg.generate(request, caseList); // レスポンス生成 return this._createResponse(request.getOutputFilePath(), caseList.length); } private _createResponse(path: string, num: number): CreateScriptResponse { let detail = new CreateScriptResponseDetail(); detail.setCaseName(path); detail.setCaseNum(num); return new CreateScriptResponse("OK.", [detail]); } }
実装クラスと紐づける
最後に、inversify.config.ts
にInterfaceと実装クラスの紐付けを定義します。ここで紐付けはこのクラスでしか行いません。
to()
で実装クラスを紐づけるのですが、toSelf()
なる関数があるらしい。(使い方は分からない...)
import "reflect-metadata"; import { Container } from "inversify"; import { TYPE } from "./Types"; import { IBaseController } from "../controller/IBaseController"; import { CreateScriptController } from "../controller/CreateScriptController"; import { ICreateScriptUseCase } from "../usecase/UseCaseInterface"; import { CreateScriptInteractor } from "../usecase/CreateScriptInteractor"; import { CaseScriptBlg } from "../business/CaseScriptBlg"; import { CaseScriptListBlg } from "../business/CaseScriptListBlg"; import { ICaseListRepository, IScriptRepository } from "../repository/RepositoryInterface"; import { CaseListRepository } from "../repository/CaseListRepository"; import { ScriptRepository } from "../repository/ScriptRepository"; const container = new Container(); // Controller container .bind<IBaseController>(TYPE.CreateScriptCtrl) .to(CreateScriptController); // UseCase container .bind<ICreateScriptUseCase>(TYPE.CreateScriptUseCase) .to(CreateScriptInteractor); // Business Logic container.bind<CaseScriptListBlg>(TYPE.CaseScriptListBlg).to(CaseScriptListBlg); container.bind<CaseScriptBlg>(TYPE.CaseScriptBlg).to(CaseScriptBlg); // Repository container.bind<ICaseListRepository>(TYPE.CaseListRepo).to(CaseListRepository); container.bind<IScriptRepository>(TYPE.ScriptRepo).to(ScriptRepository); export { container };
そして使いたいクラスをDIコンテナから取り出せば完成!
import { container } from "./common/inversify.config"; import { IBaseController } from "./controller/IBaseController"; import { TYPE } from "./common/Types"; const controller = container.get<IBaseController>(TYPE.CreateScriptCtrl); // <-- Here!!
まとめ
InversifyJSを使うと、Node.jsのアプリでもDIやるのは結構簡単ですね。 今回はCLIアプリで一からコードを書きましたが、Webアプリでフレームワークを使うときも同じように書けるはず。
Serverless FrameworkでKotlin & LambdaのサーバーレスAPIを作る
朝活.Kotlinで作ったServerless Framework × Kotlin × LambdaのサーバーレスAPIまとめです。
概要
題材
上記のSlackBotのバックエンドをLambda関数で実装する.
CI/CDファーストを目標に、なるべくも手作業を減らしてビルド&デプロイを簡単にする.
- CloudFormationを組むのは個人的にはしんどい&気軽にできるものとは思っていないので、Serverless Frameworkを使ってデプロイを行います.
- 参考: 至高のCI/CDパイプラインを実現する5つの約束
やったこと
まずアプリケーションを作成するときに悩むのが、ボイラープレートコード(フレームワーク等で必要になる典型的なコード)の扱い。 ビジネスロジックを書くことに集中できるのがサーバーレスアプリケーションの強みの1つなので、LambdaとAPI Gatewayの接続部分や、エントリーポイントのHandlerクラスはなるべく書きたくないところ。
実はServerless Frameworkのテンプレートから雛形は自動生成可能です(プラットフォームによってはない)。
sls create --help
を打って調べる限り, 使えるテンプレートは3つありますね。KotlinでJS使うテンプレートなんてあるのか...
それでは雛形をコマンドラインから作成。事前にnpm i -g serverless
でserverlessコマンドは入れておきましょう。今回は"aws-kotlin-jvm-maven"を使います。
serverless create \ --template aws-kotlin-jvm-maven \ --path matsuya-finder \ --name matsuya-finder
5秒くらいでテンプレートは作成されます。指定した--path
以下は以下のようなディレクトリ構成になっています。
. |- pom.xml |- serverless.yml |- src |- main |- kotlin |- com |- serverless |- ApiGatewayResponse.kt |- Handler.kt |- Response.kt
ApiGatewayResponse.kt
... Lambda --> API Gatewayにレスポンスを返す時のデータの格納クラス。弄らずにそのまま使うHandler.kt
... アプリケーションのエントリーポイント。ビジネスロジックはここにInjectする。Response.kt
... アプリケーションのレスポンス。data classになっている。serverless.yml
... Serverless Frameworkに管理されたアプリケーションのデプロイ定義。裏ではCloudFormationを使っている。
あとは業務ロジックのコードを好きに書きます!
最終的なコードは以下の図のような構成になりました。 アプリのコードが重くなければ、KtorやJavaliなどのWeb Application Frameworkを使うよりもスッキリしてますね!
また、serverless.yml
は以下の構成になっています。このファイルが置かれている場所で sls deploy
を発行すればデプロイ完了です。
service: chom-matsuya-finder # Base Definition provider: name: aws runtime: java8 stage: dev region: ap-northeast-1 # Packaging information package: artifact: target/matsuya-finder-dev.jar # Individual Function Definitions functions: main: handler: net.ponzmild.Handler name: matsuya-finder description: Fetch today's Matsuya menu. events: - http: method: post path: matsuya
Tips
Serverless Frameworkのコマンドを打つときは, 権限のあるプロファイルを使おう
- IAMロールをつけたのになぜかUnauthorizedとなってしまうことありますが、だいたいはIAMロールをつけたユーザーでCLIを叩いてないことが原因です。
- (使用例)
sls deploy --aws-profile your-serverless-agent
そのままデプロイすると統合リクエストのタイプが"Lambda Proxy"になる
- このタイプはリクエストのコンテントタイプを細かく指定しづらいです。
- また、雛形として自動生成される
ApiGatewayResponse.kt
はLambda Proxyタイプ用のレスポンス定義です!これ以外のタイプは別途レスポンスのクラスを用意しましょう。 serverless.yml
でintegration:lambda
を指定するか、Handlerクラス内でパースのどちらかで解決しなければなりません。- 今回は後者にしています(以下のコード)
class Handler : RequestHandler<Map<String, Any>, ApiGatewayResponse> { override fun handleRequest(input: Map<String, Any>, context: Context): ApiGatewayResponse { // API GatewayのイベントのJSONからリクエストボディのみ取り出す val inputJsonString = input.get("body") as String // 全て文字列なので, Mapに詰め替える var inputBodyMap: HashMap<String, String> = hashMapOf() val kvStrings = inputJsonString.split("&") kvStrings.forEach { val kv = it.split("=") inputBodyMap.put(kv[0], kv[1]) // 詰め替え } // And more... } }
Kotlinも結構簡単にサーバーレスなAPIを作れるんですね。他にもS3のイベントやCloudWatch Eventsにも対応できるのでできる幅も広そう。 また、Androidユーザーはフロントエンドと同じ言語を使えるからいいですね。iOSユーザーのためにSwiftでもできるのかな?
以上。
CloudWatch EventsからAWS Batchを叩くCfnテンプレートが書けないからCLI使う
CloudFormationテンプレートのYAMLファイルからAWS Batchを叩くCloudWatch Eventsのルール・トリガーを作ろうとしたらダメだったから代案としてCLIで作った話です。
CloudWatch Eventsのルールに対して、どのサービスをどのようなイベントに反応して/時間間隔で動かすのかを "ターゲット" として定義します。 折角なので他の環境でも使い回せるようにCloudFormationを使ってAWS Batchを起動するCloudWatch Eventsのターゲットを作れないかとドキュメントをみてる見ると、、、それらしい記述がない。
AWS APIとして定義はあるのかと見てみると、こっちにはTargetオブジェクトにBatchParameters
というプロパティがあるから実は裏仕様としてCloudFormationを叩けるのでは?
以下のようにCfnテンプレートを作成して実行してみます。結論としては作成できませんでした。
AWSTemplateFormatVersion: "2010-09-09" Description: AWS Batch Trigger template Resources: PzBatchScheduledRule: Type: AWS::Events::Rule Properties: Description: "Triggers AWS Batch Job" Name: "pz-aws-batch-trigger" ScheduleExpression: "cron(0 0/1 * * ? *)" State: "ENABLED" Targets: - Arn: !Sub "arn:aws:batch:${AWS::Region}:${AWS::AccountId}:job-queue/logger-queue" Id: "Pz-Batch-Target-test" RoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/service-role/AWS_Events_Invoke_Batch_Job_Queue_1960059509" BatchParameters: JobDefinition: !Sub arn:aws:batch:${AWS::Region}:${AWS::AccountId}:job-definition/scala-args-logger:2 JobName: "test-batch-job" Input: "\"{\\\"Parameters\\\":{\\\"jobType\\\":\\\"test\\\",\\\"languageCode\\\",\\\"ja\\\",\\\"app\\\":\\\"APP\\\"]}}\""
以下結果のスクリーンショットです。
Encountered unsupported property BatchParameters
...なるほど🤔 無理なのか。
ここに時間をかけてる場合じゃない。リリースと同時に作ればいいと割り切ってCLIから作ることにします。
# ルールの作成 aws events put-rule --name "test-batch-rule" --schedule-expression "cron(0 0/1 * * ? *)" # ターゲットの作成 aws events put-targets \ --rule "test-batch-rule" \ --target "Id"="batch-executor-rule-id",\ "RoleArn"="arn:aws:iam::${AWS:Account}:role/AWS_Events_Invoke_Batch_Job_Queue_1960059509",\ "Arn"="arn:aws:batch:${AWS:Region}:${AWS:Account}:job-queue/logger-queue",\ "BatchParameters"="{"JobDefinition"="arn:aws:batch:ap-northeast-1:${AWS:Account}:job-definition/scala-args-logger:2","JobName"="test-batch-name"}",\ "Input"="\"{\\\"Parameters\\\":{\\\"jobType\\\":\\\"test\\\",\\\"languageCode\\\",\\\"ja\\\",\\\"app\\\":\\\"APP\\\"]}}\""
これでなんとかCloudWatch EventsからAWS Batchを起動するルールを作成できました。今後CloudFormation経由で実行されることを期待します。。。
他にも罠があり、以下2つを見つけました。ご参考までに。
- ジョブ定義のRefの個数とCWEのRefの数が一致しないとそもそもイベントがトリガーされなかった。。。
- Refの後のキー名は先頭小文字にしないとトリガーされない。
以上。
JAWS DAYS 2019 参加レポート
日本のAWSユーザーグループであるJAWSが主催の "JAWS DAYS 2019" に行ってきました。お昼の時点で参加人数1,500人を超えていたこともあって、200人収容できるセッションでも立ち見が出るほどの盛況っぷりなのが印象的。
忘れないように会場で実際に聞いてきたセッションについて簡単にまとめておきます。
[Serverless] サーバレスで動かすトークン発行プラットフォーム
実際にトークン発行プラットフォームとして使っているEtheriumすごい的な話ではなく、如何に新しい技術を使って日々新しいものを作るかの過程の話だった。
新しいものを使うことはリスクになる上に、人・時間・知識がない中で最小限のプロダクトを作ってしまおう。サーバーレスは良いソリューションになるんだってのがこのセッションでの学び。
[Serverless] AWS Serverlessを活用したサービス監視
サーバーレスを使いつつ、監視コンポーネントとAWSのサービスの組み合わせをラップしたOSSを作った話。
最近本屋に平積みされているオライリーの "入門 監視" の内容を踏まえて、監視に必要な要素を明確にしながら自分でOSSを作ってしまおうというところがすごい。
AWSとのサービス統合でポイントになっていたのが、API Gatewayから直接Lambdaを叩かずSQSを間に挟んでいること。監視エージェントとストレージを疎結合にするための戦略なのは勉強になった。それにログやメトリクスとか膨大な量のデータを一気にAPI Gatewayで受けたらすぐにスロットリングしちゃうし、CloudWatch LogsにLambdaのサブスクリプション貼る時と同じことになるもんね。
API GatewayがLambda以外のサービスに直接リクエストできるのも初めて知った。。。
[EC2/DB] RDBリファクタリングと異種間DB移行の戦い – Amazon DMSを使った止めずにリファクタリングする手法
そーだいさん(id: Soudai)のRDBリファクタリングのセッション。
DBの寿命はアプリよりも長い。アプリが動いて数年経つと、謎テーブルの存在ややカラムに意図しない値、制約が貼ってない等々技術的な負債が貯まってくる。そんな状況でRDBをリファクタリングされた時(現在進行形らしいですが)の挑戦記録を話されていました。
2ステップ大きく分けてされていたようで、どちらのステップも興味深い。
- 現状Aurora MySQL5.6で運用しているテーブルをリファクタリングするために、トリガー(SQLが発行されたイベントに反応して実行される処理)を使う。
- MySQL5.6だと1テーブル1イベントに対して1トリガーしか貼れないので、この制約がないPostgreSQL for RDSにマイグレーションする。
MySQLからPostgreSQLに移行するときに使用するのがAWS Database Migration Service (DMS)です。 なので、現行Aurora MySQL --(DMS)--> 旧スキーマのPostgreSQL --(トリガー)--> 新スキーマのPostgreSQL の流れでマイグレーションしたと。
参照はAPI経由でいきなり新しいスキーマのPostgreSQLから実施することでModel単位に移行させるといったことは今後の業務で使えそうなTipsです。
さらに興味深かったのが、実運用のところ。平気で20~30秒(?)マイグレーションが遅延する、更新が激しいとDMSが単一障害点になってしまうといった使ってわかったデメリットなんだとか。なるほどな。
最後にそーだいさんが言っていたように、RDBリファクタリングは「覚悟」が必要とのこと。肝に命じておこう。
[Lunch Session] AWSからメール送るならSendGrid一択ですよね
自宅から歩ける範囲に平日にSendGridの勉強会をやっているのは知っていたけど、なかなか行けなかたので参加。
ランチタイムの15分セッション。SendGridそのもののアピールがほとんどでしたが、SESとSendGridの比較が乗ってて確実にメールを届けるなら良い選択肢だと認識。
[Lunch Session] クラウド時代のモニタリングといえばDatadogだよね
AWSだけでなく、一元的にモニタリング・メトリクス・ログ・APM収集できるのを再確認。2018のre:Inventで一番デカイブースを構えていたとのことだったので、ちょっと興味が湧いたな。
可愛いDatadogのステッカー貰えたし、使ってみようかしら。Mackerel、ElasticSearch/Kibanaと比較して要検討。
[DevEnv] 至高の CI/CD パイプラインを実現する5つの約束
内容は上のスライドにまとまっているけど、肝心だと思ったとこだけ抜粋 + 当日のメモ。
- パイプライン ファースト
- アプリよりも先にまずはCI/CDパイプラインを作ることを強調。
- 一発目のデプロイからアプリは雛形でいいからパイプラインでデプロイを書けることが、ROIの高い投資だと表現していた。
- パイプラインも最初から整えなくても、手元のデプロイスクリプトをまとめるだけでひとまずOK
自動化されたパイプラインを維持
- 自動化できない変更は避ける
- パイプラインにアプリ都合の複雑なオペレーションを押し込まない、アプリで吸収しちゃう
柔軟なパイプラインの維持
- 常にシンプルにキープ
- パイプラインをコード化する
パイプラインのUXの継続的改善
- CI/CDパイプラインを開発メンバーに提供するサービスとして考える
- 何が実行されて、なぜデプロイが失敗したのか分かるよう
- 時間短縮を図ろう
- 作り込みすぎて安定性を失うことを避ける
パイプラインを唯一のデプロイ方法にする
- "とりあえず手作業" は楽だが禁忌 (ビジネスが危機的な場合のみ許す)
- パイプラインが有名無実の代物になるから
[Supporter Session] 三題噺「F-Secure 基幹システムは Serverless !あと IoTセキュリティとAWSセキュリティ」
F-Secureというセキュリティソフト・コンサルティング会社の営業さんがスピーカーのセッション。なんとLT3本立てという豪華な構成に加えて、話がめちゃくちゃうまかった。
会社の事業紹介しつつAWSを使いながらセキュリティサービスをどう提供しているかというお堅い感じのサービスにも関わらず新鮮なトークでした。
ユーザーがアンチウイルスソフトの利用登録とアクティベーションを可能な限り早く行うため、かつユーザーを逃さないようにサーバーレスにしたとのこと。AWSユーザー企業ケーススタディの1つにも乗っていたのでアーキテクチャ図を見てみましたが、Kinesis とLambdaの組み合わせで実現しているのが面白い。
[Others] Infrastructure as Codeに疲れたので、僕たちが本来やりたかったことを整理する
個人的に一番登壇者の苦労に共感できたセッション。(会場の人の反応を見る限り大多数が同じような悩み・つらみを抱えていそう)
リソース管理のためにTerraFormテンプレートを書いてたけど、本当はそこにばかり時間をかけている場合じゃなくて価値あるタスクをするべきなのに辛さばかり目立つという話。
ちょうど前日・前々日とCloudFormationからCloudWatch Eventsのルールを作れないとこでハマってたので、ただただ共感でした。
ここでのセッションの要点としては、ROIを考えてInfrastructure as Codeで管理すべきこととすべきではない(割りに合わない)ことを考えましょうという点でしょう。
ROIが低いならWebコンソールやCLIからでええやんとのことでしたが、私が考えている答えとしてはほぼ同じです。ただ、本番環境でも使うサービスなら失敗してもロールバックできるように出来る限りCLIがベターかなとは思います。
[CLI] AWS CLIではじめるコマンドラインライフ 〜 正しい「運用自動化」への第一歩
えーまじ〜GUI使うのは中学生までだよね〜をかなり本気で考えている(ような) JAWS CLI支部の方のセッション。
AWSを真に理解するとはAWS APIの働きを理解することである。AWS CLIならAWS APIのほとんどを操作できるのだから、CLIで操作することこそAWSを真に理解するための道であると説いていたのが印象的。
確かに本番環境でGUIをいじるのは流石にないけど、CloudFormationで時間かけて無駄に長いスタックのYAML書くよりもCLI書いて直接操作するのが直感的だ。
- 何はともあれ、公式リファレンスを読もう。
- CLI補完機能を使おう! aws_completer をセットするだけ。
--query
オプションを使おう! 出力制御がCLIだけで完結できるから、jq
よりもいいぞ! docs.aws.amazon.com
JAWS CLI支部の勉強会開催場所は会社のすぐそばだし行こうかしら。。。
上記以外にもEKSやDevSecOps/テスト自動化のセッションと懇親会の時のLTも聞きたかったけど、時間が被って断念。。。SlideShareやSpeakersDeck、Twitterに資料が上がることを期待します。
セッション以外も嬉しかったポイントがあって、広い会場でも確実にスピーカーの声が聞こえるように一人一人スピーカーホンが配られてたのはGJだった。隣のセッションとはパーティション一枚でしか区切られてなかったから、結構声が隣から漏れてきてたし。
あとはお弁当豪華でしたね。さすが "満願全席" がテーマなだけありました。(写真は取り忘れた。。。無念)
以上。
AWS Batchに渡したCommandがプログラムの引数に渡らない時はDockerfileを見よう
お仕事でも使ってるAWS BatchでCommandに渡した値がプログラム(Javaだとmain関数のargs部分)に渡ってこなくてハマった時の対象方法メモ。 AWSの公式ドキュメントが分かりずらいので、Dockerのドキュメントも参照しながら解決しました。
Commandとコマンドライン引数の関係
そもそもの話。AWS Batchのコンテナプロパティ"Command"はDockerfileに記述されたどの部分にマッピングされるのだろうか? AWS Batchのジョブ定義のパラメータのセクションを見ると以下のように書いてあります。
このパラメータは、Docker Remote API の コンテナを作成する セクションの Cmd にマッピングし、 COMMAND パラメータを docker run にマッピングします。Docker CMD パラメーターの詳細については、https://docs.docker.com/engine/reference/builder/#cmd を参照してください。
🤔???
分かりづらいので噛み砕いて説明します。。。AWS Batchに渡されたジョブ定義のCommandはdocker run
の後に続くパラメータとして渡されます。Dockerfile内での記述でどうも引数の渡され方の挙動が変わる らしく、以下のような感じになります。
- CMDでコマンドを指定した --> ジョブ定義で指定したCommandの1番目が実行可能バイナリ、2番目以降が引数となる
- ENTRYPOINTでコマンドを指定した --> ジョブ定義で指定したCommandが全てENTRYPOINTのコマンドの引数となる
- CMDとENTRYPOINTでコマンドを指定した --> ジョブ定義で指定したCommandが全てENTRYPOINTのコマンドの引数となる
3番目のパターンに関してはDockerのドキュメントの方に以下のようなコメントがありました。
If the image also specifies an ENTRYPOINT then the CMD or COMMAND get appended as arguments to the ENTRYPOINT
例えば下のようなDockerfileに対してジョブ定義のコマンドで "hoge fuga"と渡した場合は java -jar /app/application.jar hoge fuga
となります。
混乱しないようにするためには、Dockerfile内はENTRYPOINTのみ指定して、ジョブ定義は引数扱いにした方が良いですね。
FROM openjdk:8-alpine COPY target/scala-2.12/args-logger-1.0.0.jar /app/application.jar ENTRYPOINT ["java", "-jar", "/app/application.jar"]
今回引っかかったところはDockerfileでCMDを指定したつもりでコマンドを書いていたら実はENTRYPOINTだったから引数が欠けてしまったというオチでした。。。
CommandとParameterの関係
ついでに、ジョブ定義にはCommandとParameterというパラメータがあります。関係性としては、Command内に変数を埋め込んだ場合に実際の値としてParameterの値を入れることになります。値を外から(CloudWatch Eventsで指定した値とか)Parameterとして注入することでアプリ内で動きを変えるといった使い方ができるようです。
DockerfileとCommandとParameterを設定して動かす
実際に動かして確かめます。渡された引数をログ出力するだけのScalaのアプリを使います。 commandパラメータはジョブ定義で以下のように ["Ref::jobType", "Ref::language"] として変数化しました。
実際に渡したパラメータは下図の通り。
実際に動かしてみると...
確かにcommandパラメータにParameterを埋め込んだ値で引数が全て渡っていますね。
GCP Professional Data Engineer認定取得しました
GCP Professional Data Engineer認定試験に合格してきました! 認定取得にあたり、取得するまでのポイントや試験のことを書いておきます。
事前に準備したこと
- Coursera
- 各サービスのコンセプトやらユースケースを細かく詳しくレクチャーしてくれる。
- 分量が多いので、概要を掴むくらいを期待するなら重すぎるかも。 - 間に手を動かすパートと小テストがあったから少しは自信がつく
- qwiklabs - 手を動かす用. アカウントは制限時間ありで用意してくれる
押さえるべきサービス
データに関するサービスだけでなく、セキュリティやログ出力設定まで聞かれます(システムを作る/使う上では基本だもんね)。
ざっと列挙するだけでも以下のサービスを一通り抑える必要がありました。 もちろん、現時点で使っているサービスがあるならそれを掘り下げると良い。自分みたいにほとんど知らないなら、チュートリアルの一つ目だけやって触るだけでもしたほうがいいです。
特にBigQuery, IAM, ストレージ/データベースサービスの使い分け, 機械学習の方法(特徴量エンジニアリングとか)は押さえておく必要がありました。
- IAM (Identity and Access Management)
- BigQuery
- いわゆるデータウェアハウス, 引くレベルで高速にデータをSQLで探索できる。
- テーブルやビューの作成を作成してデータを保存できるけど、データの検索に重きを置いている印象。
- Google Cloud Storage
- Cloud Pub/Sub
- Publish-Subscribe型のメッセージングサービス。
- プッシュ通知的なpush型, キュー的なpull型の2タイプあり。
- Cloud Dataflow
- Cloud Datalab
- Cloud Dataproc
- Cloud Dataprep
- Cloud ML Engine
- Vision API
- Stackdriver Logging / Monitoring
- 名前の通り、ログやモニタリングのサービス。
- ほとんどのサービスでは組み込みでStackdriver Loggingでログを見れるようにはなってる。
- Cloud Bigtable
- Cloud SQL / Cloud Spanner
- Cloud SQLはMySQL/PostgreSQLのホスティングサービス。
- Cloud SpannerはスケールアップするSQLデータベース。
- Cloud Composer
- Cloud Storage Transfer Service
試験受験したとき
大きく分けて以下2種類。出題数的には前者8割、後者2割でした。ケーススタディは試験の公式サイトに載っているやつそのままです。
時間は2時間ありましたが、1.5時間で一通り解き終わりました。全部見直しするには少し時間が足りなかったです。。。 認定試験のサイトだとケーススタディを猛烈プッシュしていましたが、実際には各サービスについての一問一答形式が多くて焦ります。AWSの試験を受けたことがある方には同じ形式の出題だったと言えば分かりますかね笑
また、ケーススタディはビジネス要件(履歴データを集約して予測分析したい、本番環境を柔軟にスケールさせたい)、技術要件(出来るだけマネージドサービスを使う、Hadoopワークロードはそのまま移行したい etc...)が出題として問われたり、時にはヒントになっていました。 ただ、受験した会場特有なのか分かりませんが、左に問題、右にケーススタディの構成や要件が表示という2窓構成でした。めっちゃ見づらかった。。。
認定試験を通しての学び
Kubernatesを触るために最初に使い始めたGCPでしたが、今回の認定試験の学習を通してデータ分析や分析基盤の構築について学べました。 特に以下のことについては今まで触ったことのあるクラウド(IBM Cloud, AWS)以上に理解できたと自信を持てます。
- 分析用途のデータ処理パイプライン構築にはどのようなサービスを組み合わせれば良いか。
- 機械学習サービスは他のクラウドベンダーのAIとは何が違うのか。どのように使うのか。事前に使えるモデルは何か。
- 分析に使うデータウェアハウスのBigQueryの特徴。
- ストレージの使い分け。
個人的にはデータをSQLで分析するBigQueryやDataflow, Dataprepと他のクラウドで処理したデータを連携させたりできないかとアイデアを膨らませるいいきかっけになったかと。 例えばAWSとGCPの2つを使ってアプリのログのデータ分析をマルチクラウドできるんじゃないか? (冗長かもしれないけど)
- AWS ECSで動くアプリケーションのログをCloudWatch Logsに集約
- CloudWatch Logsのログ生成をAmazon SNSのトピックに流し込む
- SNSをサブスクライブするLambda経由Kinesis Firehoseにデータを入れる
- Kinesis FirehoseからAWS S3にログをストリーミングで直接保存
- Cloud Storage Transfer ServiceでS3からGCSにログデータを転送
- DataflowでGCSからBigQueryに流し込む
- BigQueryで分析
後日談
認定通ったら社内のPrimary Job Role Specialtyが Application Developer: Google Cloud Microservicesになってました⸜(* ॑꒳ ॑* )⸝笑 今後はGCP Associate Engineer取ってみたいですね。あとはAWS SAAも!