【Node.js】モジュールの作成方法
はじめに
本記事の目的
Node.jsでWEB-APIサーバを立ち上げたが、一部の処理を別のモジュールとして切り出したい場合がある。自分でモジュールを定義して、処理を呼び出す方法をメモ。
前提
- Node.jsとnpm(Node.jsのパッケージマネージャ)がインストールされていること。
- Node.js + Expessを利用してHTTPサーバを立ち上げていること。
前回の記事 tomiko0404.hatenablog.com
module.exportsを利用してモジュール作成
モジュール作成
モジュール用のファイルを作成する。
app.js
の配下にlib
フォルダを作成し、その配下にmydouble.js
を作成する。
lib/mydouble.js
module.exports = (num) =>{ return (num * 2); }
module.exports
module.exports
に関数を代入することで、モジュールとしてエクスポートできる。
モジュールの呼び出し
app.jsに以下のように記載する。
requireした関数をmydouble
に代入して、mydouble(10)
で呼び出している。
app.js
const express = require("express"); const portNumber = 8000; const app = express(); // ★mydoubleモジュールをrequireする const mydouble = require("./lib/mydouble.js"); // ★mydoubleを呼び出す app.get("/", (req, res) => { res.status(200).send("My number is " + mydouble(10) + "."); }); app.listen(portNumber); console.log(`PortNumber is ${portNumber}`);
結果確認
10×2の20が返却されていることが確認できた。
exportsを利用する場合
exports
を使用して、各プロパティを定義することもできる。
lib/mycalc.js
exports.myadd = (num) => { return num + 100; }; exports.mysubtract = (num) => { return num - 100; }; exports.numbers = { a: 1, b: 200, c: 10000, };
app.jsでの利用方法
以下のように利用した。
app.js
// ★mycalcモジュールをrequireする const mycalc = require("./lib/mycalc.js"); // ★mycalc.[プロパティ名]で呼び出す app.get("/", (req, res) => { let data = `mycalc.myadd(10):${mycalc.myadd(10)} `; data = data + `mycalc.mysubtract(300):${mycalc.mysubtract(300)} `; data = data + `mycalc.numbers.a: ${mycalc.numbers.a}`; res.status(200).send(data); });
参考資料
Node.js 用モジュールの作り方(require でロード可能な Node モジュールを作成する) | まくまくNode.jsノート
exports と module.exports の違い - Qiita
おわりに
Node.jsで自分で定義したモジュールを呼び出す方法がわかった。
Node.js + ejsでフロントエンドを作成する方法メモ
はじめに
本記事の目的
前回、Node.jsを利用してWEB APIサーバを立ち上げた。
流れで、テンプレートエンジンを利用してフロントエンドをNode.jsで実装する方法も学んだので、メモしておく。
以下の講座を参考に実施している。
Node.js + Express で作る Webアプリケーション 実践講座 | Udemy
前提
- Node.jsとnpm(Node.jsのパッケージマネージャ)がインストールされていること。
- Node.js + Expessを利用してHTTPサーバを立ち上げていること。
前回の記事 tomiko0404.hatenablog.com
- はじめに
- テンプレートエンジンを利用しない場合の書き方
- テンプレートエンジン(ejs)を使った作成方法
- ejsに外から値を渡すパターンの作成方法
- ejsの文法
- ejsの記載
- 結果確認
- ejs内でJavaScriptを記載するパターンの作成方法
- 【補足】その他のejs文法
- 【参考】ejs以外のテンプレートエンジンの例
- おわりに
テンプレートエンジンを利用しない場合の書き方
画面サーバとしてNode.jsサーバを利用する場合、ブラウザが理解できるようにHTML形式のデータを返してあげる必要がある。
前回学んだ方法で実装しようとすると以下のようになる。
app.js
const express = require("express"); // ポート番号を変数に設定 const portNumber = 8000; // app変数にexpress関数を設定する const app = express(); app.get("/", (req, res) => { let data = "<!DOCTYPE html>\r\n"; data += "<html>\r\n"; data += "<head>\r\n"; data += " <meta charset='UTF-8'>\r\n"; data += " <title>startup-ay-sugiura</title>\r\n"; data += "</head>\r\n"; data += "<body>\r\n"; data += " <h1>Hello World</h1>\r\n"; data += " <p>ejsを使わずにHTMLをres.send(data)</p>\r\n"; data += "</body>\r\n"; data += "</html>"; res.status(200).send(data); }); // リクエストを待ち受ける app.listen(portNumber); console.log(`PortNumber is ${portNumber}`);
data
にHTMLを1行ずつ追加していき、最後にres.status(200).send(data)
を実行している。
node app.js
を実行し、Postmanで応答を確認すると、HTMLが返却されたことがわかる。
ブラウザでは以下の表示。
テンプレートエンジン(ejs)を使った作成方法
今回はejsを利用する。
テンプレートエンジンも奥が深そう。
参考文献
anken-hyouban.com
ejsのモジュールをインストール
npmでejsをインストールする。
$ npm install ejs --save
ejsファイルを作成する
app.js
と同じ階層に、views
フォルダを作成し、その中にindex.ejs
を作成する。
後で利用するrender
メソッドでは、パスを指定しないとデフォルトでviews
フォルダ配下を見に行くので、このフォルダ構成にしておくこと。
app.jsでejsファイルを呼び出す
app.js
は以下のように記載する。
//expressをrequireする const express = require("express"); // ポート番号を変数に設定 const portNumber = 8000; // app変数にexpress関数を設定する const app = express(); // app.setでコンフィグを設定する app.set("view engine", "ejs"); //sendではなくrenderを使う app.get("/", (req, res) => { res.status(200).render("index.ejs"); }); // リクエストを待ち受ける app.listen(portNumber); console.log(`PortNumber is ${portNumber}`);
app.set()
コンフィグを設定するメソッド。
app.set("view engine", エンジン名)
res.render()
http.ServerResponseクラスのメソッド。
res.render(viewファイル(viewsフォルダからの相対パス), locals(引き渡したい値))
index.ejsにHTMLを記載する
以下のように普通のHTMLを記載してみる。
views/index.ejs
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>サンプルay-s</title> </head> <body> <h1>Hello world</h1> <p>ejsでrenderする</p> </body> </html>
問題なくHTMLが返却された。
ejsに外から値を渡すパターンの作成方法
app.jsへの記載
render関数を利用している部分を書き換え、以下のようにする。
//expressをrequireする const express = require("express"); // ポート番号を変数に設定 const portNumber = 8000; // app変数にexpress関数を設定する const app = express(); // app.setでコンフィグを設定する app.set("view engine", "ejs"); //★renderメソッドにオブジェクトを渡す app.get("/", (req, res) => { res.status(200).render("index.ejs",{title: "ejsに外から変数を渡すパターン"}); }); // リクエストを待ち受ける app.listen(portNumber); console.log(`PortNumber is ${portNumber}`);
ejsの文法
出力タグ (エスケープ有り)
<%=
と%>
でくくると、変数の値をそのまま出力する。
ただし<
などはエスケープされる。
<%= title %>
出力タグ(エスケープ無し)
<%-
と%>
でくくると、変数の値をそのまま出力する。エスケープもされない。
<%- title %>
ejsへの変数の渡し方
res.render
メソッドの第二引数で、オブジェクト形式として引き渡すことができる。
ejsの記載
ejsに<h2>タイトル:<%= title %></h2>
を追加。
views/index.ejs
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>サンプルay-s</title> </head> <body> <h1>Hello world</h1> <h2>タイトル:<%= title %></h2> <p>ejsでrenderする</p> </body> </html>
結果確認
変数で渡した値もHTMLに含まれて返却された。
ejs内でJavaScriptを記載するパターンの作成方法
ejsの文法
スクリプト <% %>
<%
と%>
でくくるとJavaScript扱いとなる。
<% 処理 %>
app.jsの記述
title項目だけでなくtextsフィールド(配列)もejsに引き渡す。
app.js
(app.get文だけ記載)
app.get("/", (req, res) => { res.status(200).render("index.ejs", { title: "ejsに外から変数を渡すパターン", texts: [{ text: "テキスト1" }, { text: "テキスト2" }, { text: "テキスト3!" }], }); });
ejsの記述
ejs中で、受け取ったtexts
フィールドをfor文で繰り返し呼び出し、<li>
要素に入れる。
ここで、<li>
や</li>
は、スクリプトタグ<% %>
で囲ってはいけないため、複数のタグを記載する必要がある。
views/index.ejs
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>サンプルay-s</title> </head> <body> <h1>Hello world</h1> <h2>タイトル:<%= title %></h2> <ul> <% for (let data of texts) { %> <li><%= data.text %></li> <% } %> </ul> <p>ejsでrenderする</p> </body> </html>
結果確認
結果の確認。for文が実行されて3要素返却された。
ブラウザでも表示された。
【補足】その他のejs文法
終了タグについて
通常の終了タグは %>
だが、 -%>
とすると改行を削除して出力される。
<%
のエスケープについて
<%
という値を返却したいときは、<%%
と記載することでエスケープできる。
【参考】ejs以外のテンプレートエンジンの例
ejs以外にも以下のようなテンプレートエンジンがある。
- pug (jade)
- hjs
- hbs
pugはExpressのデフォルトテンプレートエンジンとのこと。 また、昔はjadeと呼ばれていたが、呼び方が変わったらしい。
これらを使いたい場合は(例:hjsを利用)、先ほどのejsを利用した記載から、以下の変更が必要になる。
index.ejs
というファイル名をindex.hjs
に変更する- app.setの中身を書き換える
app.set("view engine", "hjs");
- res.renderを書き換える
res.status(200).render("index.hjs",{title: "hjsを利用するパターン"});
おわりに
テンプレートエンジンを利用して、HTMLを返却する方法をメモした。
HTTPリクエストツールPostmanの利用/インストール時のエラー対応
はじめに
Node.jsの勉強をしていたところ、HTTPリクエストをPOSTメソッドで投げたい場面が出てきたため、Postmanを利用することにした。手順のメモ。
【参考】GETでHTTPリクエストを投げる方法
ブラウザを立ち上げて、IPアドレスまたはURLを入力する。
Postmanを利用してPOSTのリクエストを投げる
デスクトップ版のインストール
ブラウザ版は使いこなせなかったので、デスクトップ版をインストールする。
ダウンロードしてきた.exeファイルをダブルクリックする。 待っていると自動でインストールが終わる。(待つだけなので少し不安になる)
アプリ起動が上手くいかない場合
一度目に実施した際はインストール後のアプリがうまく起動しなかった。
Postman Agent をダブルクリックしても何も表示されないし、タスクマネージャーにも表示されなかった。
上記のサイトからインストールし直したところ、うまくいった。
また、ショートカットが2種類作成されたが、起動時は「Postman」のショートカットを選ぶ必要がある模様。
アップデート
アップデートはひとまず「Dismiss」を選択。
サインイン
「Create your account or sign in later?」 という選択肢もあるので「Skip and go to the app」をクリックする。
HTTPリクエストを選択
右上の「New」→「HTTP Request」をクリックする。
お試しリクエストを投げる
試しにGETリクエストを投げてみる。 お試し用のAPIを提供してくれている、JSON PLACE HOLDERのサイトを利用。
今回はhttps://jsonplaceholder.typicode.com/users
のURLを利用する。
JSON Place Holder jsonplaceholder.typicode.com
「SEND」を押すとちゃんとリクエストが返ってきた。
自分で立ち上げたNode.jsサーバにリクエストを投げる
以下のようにgetメソッドのみ受け付けるサーバを立ち上げた。
app.js
var express = require("express"); var app = express(); app.get("/", (req, res) => { res.status(200).send("Hello World."); }); app.listen(8000);
GETリクエストを投げる
GETリクエストを投げると、値が正常に返却された。
POSTリクエストを投げると、404エラーが返却された。
おわりに
Postmanを利用してHTTPリクエストを投げられるようになった。
PowerShellを管理者権限で実行できているか確認するコマンドメモ
はじめに
今回の記事の目的
PowerShellを管理者権限で実行できているのか確認するコマンドのメモ。
確認方法
以下のコマンドを実行し、
$ ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
True
なら管理者権限で実行できている。
参考文献: techexpert.tips
PowerShellの管理者権限での実行方法
PowerShell起動時に、右クリック→管理者権限で実行を選択する。
背景
以下の記事でも書いたように、npm install
やyarn install
しようとすると、symblic linkの作成不可エラーが発生することがある。
An unexpected error occurred: "EPROTO: protocol error, symlink '../../../mime/cli.js' -> '/home/vagrant/workspace/backend-practice/front_react_back_node/node_modules/send/node_modules/.bin/mime'".
PowerShell上にvagrantを立ち上げている場合、Powershellを管理者権限で実行しないと このエラーが発生する。
VS Codeのターミナルでの確認
VSCodeのターミナルを利用していたのだが、管理者権限でのPowerShell実行ができていないことが分かった。 VSCode自体を管理者権限で起動しても、状況は変わらず。
おわりに
PowerShellを管理者権限で実行できているか確認することができた。 標準のターミナルが見にくいので、VSCodeでできると良かったのだが…。現状はPowerShellを右クリックして起動するしかなさそう。
時間ができたら他のやり方も調べてみたい。
CodePipelineを利用してECS Fargateでブルーグリーンデプロイメントする
はじめに
今回の記事の目的
CodePipelineを利用してECS Fargateでブルーグリーンデプロイメント(Blue-Green Deployment)する。
まず、ビルド、デプロイについて単体で理解し、それらの設定をCodePipelineに組み込んでいった。かなり苦労したので、エラーの内容も合わせて紹介する。
順々にやっていったことで20000字の超大作になってしまったが、チュートリアルの通りにやるだけだとなかなか理解できないので、本記事の流れで理解していくのがおすすめ。
主に参考にしたチュートリアル:
https://pages.awscloud.com/rs/112-TZM-766/images/AWS_CICD_ECS_Handson.pdf
前提条件
以下の記事で作成したFargateを利用する。 ALB経由でインターネットからアクセス可能なコンテナを立ち上げた。 tomiko0404.hatenablog.com
また、コードコミットとの連携は以下の記事の通り完了している。 tomiko0404.hatenablog.com
【参考】CodePipelineを使用せずイメージだけ作り直した場合の動作確認
dockerイメージをECRにプッシュ
アプリを変更し、dockerイメージをビルドし直したら、ECRにpushする。 ECR上でもlatestが最新となった。
この状態でALBにHTTP接続してみてもアプリは変更前(旧バージョン)の状態であった。
最終的に用意する必要があるファイル
元々作っていたプロジェクトフォルダ直下に、以下3ファイルを追加する必要がある。詳細は実施内容の中で記述する。
- buildspec.yml
- appspec.yml
- taskdef.json
これらのファイルはCodeCommit上のリポジトリに存在しないと意味が無いので注意。 作成したり修正したりするたびに、pushする必要がある。
buildspec.yml
buildspec.ymlとは、CodeBuildがビルド時に実行する処理を記載するファイル。
チュートリアルを通してCodeBuildを理解する #reinvent | DevelopersIO
appspec.yml
appspec.ymlとは、CodeDeployを使ってデプロイする際の条件を記載したもの。
taskdef.json
taskdef.jsonとは、「タスク定義」を記載したもの。
- はじめに
- 最終的に用意する必要があるファイル
- CodeBuildとCodeDeployの理解
- CodePipelineを使った自動デプロイ(この記事の本番)
- 動作確認
- エラー対処のご紹介
- おわりに
- その他参考資料
CodeBuildとCodeDeployの理解
CodePipelineは、ソースコードのコミット→ビルド→デプロイの3ステップを自動で行ってくれるものである。
そもそもビルド、デプロイの概念を理解したいため、ビルド(Code Build)、デプロイ(Code Deploy)のステップをそれぞれ手動実行で利用してみる。
CodeBuildを利用してみる(手動)
IAMポリシー作成
「CodeBuildが」「ECRに」アクセスするためのIAMポリシーを作成する。
公式サイト( CodeBuild のDocker サンプル - AWS CodeBuild ) からのコピペで実施しようとしたがうまくいかなかった(ECRログインで権限エラー)ので、とりあえずフルアクセス権限にした。
フルアクセス権限作成
IAMのマネジメントコンソール→「ロールを作成」→「AWSサービス」→「CodeBuild」
Administrator Accessのポリシーをアタッチする。
最終的に以下のようになっていた。CodeBuildでIAMロールを設定すると自動で必要なポリシーもつくと思われる。
【参考】公式サイトの内容
公式サイトでは、ポリシーに以下JSONを追加するようにとのことだった。うまくいかなかった原因は不明。
{ "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:CompleteLayerUpload", "ecr:GetAuthorizationToken", "ecr:InitiateLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart" ], "Resource": "*", "Effect": "Allow" },
buildspec.yml 作成
プロジェクトフォルダ直下(Dockerfileと同じ場所)に作成した。 公式サイト(CodeBuild のDocker サンプル - AWS CodeBuild)からのコピペ。
これを見ると、実行しているコマンドは手動でECRにプッシュしていた時と同じことをしているとわかる。(一部環境変数になっているが、後で設定する。)
version: 0.2 phases: pre_build: commands: - echo Logging in to Amazon ECR... - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
CodeBuildプロジェクトを作成する
マネジメントコンソールの「CodeBuild」→ 「ビルドプロジェクトを作成する」。
公式サイト:
ビルドプロジェクトの作成 (コンソール) - AWS CodeBuild
に従って実施。
- 環境イメージは「マネージド型イメージ」。
- オペレーティングシステムは「Ubuntu」、ランタイムは「Standard」、イメージは「aws/codebuild/standard:4.0」、イメージのバージョンは最新のものとした。(公式サイト( CodeBuild のDocker サンプル - AWS CodeBuild ) より)
- 特権付与(Privileged)にチェックする
- ソースの設定
- ビルドバッジは無効にした。
- サービスロールは、先ほど作成した「ECRにアクセスできるロール(adminをつけたロール)」を設定する。
- ビルド仕様は「buildspecファイルを使用する」を選択。オプションのBuildspec名は記載不要。デフォルトでbuildspec.ymlを利用するようになっているため。
- アーティファクトは「なし」とした。
- 環境変数の設定。buildspecファイルに記載した環境変数を、ECRに手動でpushするときのコマンドと見比べながら以下のように設定。
パラメータ | 値 |
---|---|
AWS_DEFAULT_REGION |
ap-northeast-1 |
AWS_ACCOUNT_ID |
[AWSアカウント(数字12桁)] |
IMAGE_TAG |
latest |
IMAGE_REPO_NAME |
ay-s-react-practice-6 (ECRのリポジトリ名) |
ビルドの実行
「ビルドを開始」で手動ビルド(=CodeCommitからECRへのpush)を実施する。 何とか成功。
失敗するときはビルドプロジェクトに設定したIAMロールに、ECRへのアクセス権限があるか要確認。
ECR上に新たなイメージがビルドされていることを確認した。
これでビルドは完了。
CodeDeployを利用してみる(手動)
以下記事で作成したサービスを、CodeDeployを使ってデプロイする。
ECS(Fargate)でコンテナを起動その2(ALBを利用したルーティング&CodeDeployの下準備) - エンジニアを目指す日常ブログ
前提として必要な設定
前回の記事で完了しているが、事前に必要な設定として以下のようなものがある。
- Fargateサービスの作成時にALBが設定されていること。
- CodeDeploy用のIAMロールが作成されていること。
- Fargateサービスの作成時に、デプロイ方法をBlue/Greenデプロイメントと設定していること。
- Fargateサービスの作成時に、「CodeDeploy のサービスロール」にCodeDeploy用IAMロールを設定していること。
公式サイト「CodeDeploy のBlue/Greenデプロイメントを実行する」以降の手順を実施する。
AWS CodeDeploy による AWS Fargate と Amazon ECS でのBlue/Greenデプロイメントの実装 | Amazon Web Services ブログ
タスク定義の新バージョンを作成する
タスク定義から、現行バージョンのタスク定義にチェックを入れ「新しいリビジョンの作成」をクリック。Nameタグのみ、値を少し変えて作成完了する。
サービスの更新を手動で実行
ECSコンソールの「サービス」→立ち上げているサービスにチェック→「更新」ボタンをクリック。
サービスの設定画面
- リビジョンは2(latest)に変更する。
デプロイメントの設定は変更なし。
ネットワークの設定も変更なし。
オートスケーリングも設定なしのまま。
- サービスの更新を実行する。
デプロイ開始
CodeDeployによるBlue/Greenデプロイメントが開始した。
デプロイに失敗した・・・・・・・
失敗したので、ロールバックしてくれている模様。つまり、今リリースしている資材には影響を与えていない。
デプロイ失敗のログ確認
CodeDeployのコンソール画面にはログが出てこなかったが、ECSコンソール画面の「サービス」→「ログ」のタブからログを確認することができた。
また、「タスク」タブで「Stopped」を選ぶと、起動しようとして停止を繰り返すタスクがたくさん存在していることがわかる。停止を繰り返して60分たったので、デプロイがタイムアウトし、失敗となった。
デプロイ失敗の原因
Dockerfileの中身にミスがあった。CodeBuildの失敗時に色々試していた時に、コンテナのWORKDIRを変更してしまっていた。その結果/app
フォルダ内にあるpackage.json
が見つからずエラーとなっていた。
FROM node:14.15.4-slim RUN apt-get update && apt-get install -y locales \ && locale-gen en_US.UTF-8 \ && localedef -i en_US -f UTF-8 en_US.UTF-8 \ && echo "export LC_ALL=en_US.UTF-8" >> ~/.bashrc \ && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime COPY ./app /app WORKDIR /app RUN npm install ENV PORT 80 CMD ["npm", "start" ] WORKDIR / ←←これが原因だったため、削除 RUN apt-get install -y \ wget \ curl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
Dockerfileを修正し、CodeCommitにpush→CodeBuildを手動実行→サービスの更新を再実行した。
サイトを参考にした。
CodeDeployを使用したデプロイがタイムアウトとなり失敗する - あとらすの備忘録
デプロイに成功
デプロイに成功した。このまま1時間待機して問題が無ければ元のタスクが終了する。
手動で「元のタスクセットを終了」することもできた。
タスクの起動状況を見ると以下のように遷移。一時的に2つのタスクが起動し、デプロイ終了すると1つになる。
ALBにアクセスすると、修正版のWebアプリが表示された。Deploy画面のStep2まで完了した時点で、ALBの向き先は新バージョンのタスクに変更されていた(新バージョンのアプリを閲覧できた)。
CodePipelineを使った自動デプロイ(この記事の本番)
本構成のCodePipelineの役割
- CodeCommit上のソースコードの変更を検知して、パイプラインを開始してくれる。
- パイプラインが開始すると、CodeBuildでdockerイメージ化&ECRへのイメージプッシュを実施(そのようにbuildspec.ymlを記載している)してくれる。
- 続いて、パイプラインでCodeDeployを動かしてFargateのサービスを更新してくれる。
パイプラインの作成
パイプラインを作成する。AWSマネジメントコンソールの「CodePipeline」→「新規のパイプラインを作成する」。
サービスロール
- サービスロールは新規作成する。
ソースプロバイダ
- ソースプロバイダーは「CodeCommit」
- プロジェクトのリポジトリとブランチ名を設定する。
ビルドステージ
- ビルドステージを追加する。
- プロバイダは「AWS CodeBuild」を選択する。
- プロジェクト名は先ほど作ったBuildプロジェクトを指定した。
デプロイステージ
- デプロイステージを追加する。
- デプロイプロバイダーは「Amazon ECS(ブルー/グリーン)」を選択。
- AWS CodeDeployアプリケーション名を選択する。アプリケーションとは、CodeDeployでのデプロイの設定をまとめたもの。
※【利用中のECSサービスの頭に「AppECS-[クラスタ名]」が追記されたアプリケーション名】が選べるようになっていた。これは先ほど手動でデプロイしたときに作成されたもの。 - AWS CodeDeployデプロイグループを選択する。デプロイグループとは、デプロイする単位をグループ化したもの。
※【利用中のECSサービスの頭に「DgpECS-[クラスタ名]」が追記されたデプロイグループ名】が選べるようになっていた。これも先ほど手動でデプロイしたときに作成されたもの。以下のような内容が設定されていた。
ひとまずパイプラインの作成を完了した。
デプロイステージの編集
taskdef.jsonファイルを作成する
先ほど(タスク定義の新バージョンを作成する)で作成したタスク定義をJSON形式でコピーしてきて、プロジェクトフォルダ直下に新規作成したtaskdef.json
ファイルに貼り付けた。
その後、
"image": "イメージのタグ名",
となっている部分を
"image": "<IMAGE1_NAME>",
と書き換える。
appspec.ymlファイルを作成する
プロジェクトフォルダ直下にappspec.yml
を作成し、公式ドキュメントに従い、以下の通り記載する。
公式ドキュメント:
チュートリアル: Amazon ECR ソースと、ECS と CodeDeploy 間のデプロイを含むパイプラインを作成する - AWS CodePipeline
コンテナ名は、taskdef.jsonで定義したタスクの中で作成しているコンテナ名である必要がある。
appspec.yml
version: 0.0 Resources: - TargetService: Type: AWS::ECS::Service Properties: TaskDefinition: <TASK_DEFINITION> LoadBalancerInfo: ContainerName: "ay-s-ReactPrc6-forALB-vpc16-container" ContainerPort: 80
デプロイステージのアーティファクトを設定する
アーティファクトとは、CodePipeline上で前のステージから次のステージに引き渡すもの。(例:SorceからBuildeへ)
デプロイステージのアクションを編集する。
pipelineの「編集」→「ステージを編集する」→「Deploy」の編集ボタンをクリックする。
- 入力アーティファクトを「Source Artifact」と「Build Artifact」とする。
- Amazon ECS タスク定義を「SourceArtifact」の「taskdef.json」とする。
- AWS CodeDeploy App Spec ファイルを「SourceArtifact」の「appspec.yml」とする。
- 入力アーティファクトを持つイメージの詳細を「Build Artifact」とする。
- タスク定義のプレースホルダー文字を「IMAGE1_NAME」とする。
imageDetail.jsonを作成する
イメージファイルのURLを指定するためのimageDetail.jsonを作成する。 imageDetail.jsonは、ビルドステージで自動作成させるため、buildspec.ymlにファイル作成のための処理を追記する。
※今回はBlue/Green デプロイのためimageDetail.json
だが、ローリングデプロイの場合は
imagedefinitions.json
となり、フォーマットも異なるので注意。(原因②artifactのファイル名も参照)
まず、buildspec.ymlのpost_build:
の処理の中に以下を追記。
- printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
イメージURLをimageDetail.jsonに書き込んでいる。
また、ファイル末尾に以下を追記。
artifacts: files: imageDetail.json
全文は以下となる。
buildspec.yml
version: 0.2 phases: pre_build: commands: - echo Logging in to Amazon ECR... - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - echo Writing image definitions file.... - printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json artifacts: files: imageDetail.json
Build後、ArtifactファイルはS3に保存されているため、ダウンロード可能。
中身は以下のように記載されている。
imageDetail.json
{"ImageURI":"XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/ay-s-react-practice-6:latest"} ※XXXXXXXはアカウントID
設定ファイルをコードコミットにpush
appspec.yml
、buildspec.yml
、taskdef.json
を下記の通り配置できたので、コードコミットにpushする。
$ git add . $ git commit -m "設定ファイルを作成" $ git push origin_codecommit master
コードコミットの設定:
tomiko0404.hatenablog.com
動作確認
ソースコードを変更して、CodeCommitにプッシュ。
※実際は、下記のとおり問題にぶつかり続けたので、各種設定ファイルの変更→コミットをかなりの回数実施しています。
$ git add . $ git commit -m "ソースコードを変更" $ git push origin_codecommit master
やっとできた!
Deployが進行中になっているのは、1時間の切り戻し待機中(Blue/Greenデプロイ)のため。
置き換えタスクセットがトラフィックを処理していればOK。
ブラウザから確認
ALBのDNS名にアクセスすると、変更が反映されていた。
DNS名の確認方法:
ECS(Fargate)でコンテナを起動その2(ALBを利用したルーティング&CodeDeployの下準備) - エンジニアを目指す日常ブログ
エラー対処のご紹介
ハマったエラーの紹介。
※ここまでの章は、ハマったエラー原因は解消した状態での結果を載せている。
エラー#1:BuildArtifactへのアクセス拒否
デプロイステージで以下のエラーが発生。
You are missing permissions to access input artifact: BuildArtifact.
原因と解決策
buildspec.yml内でBuildステージのアーティファクトを作成できていなかったことが問題。imageDetail.jsonを作成するの手順を実施することで解消。
エラー#2 BuildArtifactの読み込み時にエラー
なんとかBuildArtifactを作れたが、以下エラーが連発。
Exception while trying to read the image artifact file from: BuildArtifact.
原因①イメージ名の環境変数間違い
環境変数を$IMAGE_REPO_NAME
と自分でCodeBuild上で設定していたが、チュートリアルに沿って$REPOSITORY_URI
にしてしまっていた。
原因②artifactのファイル名
imagedefinition.jsonではなく、imageDetail.jsonでないとだめ。
原因③artifactファイルの中身のパラメータ名違い
チュートリアルがECS標準デプロイ前提だったので、中身の書き方もそのまま間違えていた。
imagedefinitions.json
の場合(誤)
[ { "name": "sample-app", "imageUri": "11111EXAMPLE.dkr.ecr.us-west-2.amazonaws.com/ecs-repo:latest" } ]
imageDetail.json
の場合(正)
{ "ImageURI": "ACCOUNTID.dkr.ecr.us-west-2.amazonaws.com/dk-image-repo@sha256:example3" }
artifactファイルの書き方
イメージ定義ファイルのリファレンス - AWS CodePipeline
エラー#3:コンテナ名まちがい
デプロイステージで以下のエラーが発生。
The ECS service cannot be updated due to an unexpected error: The container ay-s-ReactPrc6-forALB-vpc16-container-codepipeline does not exist in the task definition. (Service: AmazonECS; Status Code: 400; Error Code: InvalidParameterException; Request ID: XXXXXXXX; Proxy: null). Check your ECS service status.
原因と解決策
appspec.yml
ファイルの中でContainerName:
を設定する際、勝手に名前を付けてしまっていた。エラーが出たため、タスク定義の中で設定されているコンテナ名に修正した。
エラー#4
デプロイを開始したが、その後タスクが起動と停止を繰り返す現象。
以下の状態で止まり、60分でタイムアウトする。
さらに、今回はログが出る前にタスクがStopしている模様。
ただし、タスクのステータス画面に怪しげな文言が出ていたので確認。
STOPPED (CannotPullContainerError: inspect image has been retried 1 time(s): failed to resolve ref "docker.io/library/ay-s-react-practice-6:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: ...)
原因と解決策
ECSのタスク定義画面から、コンテナの設定を見てみると、「イメージ」にちゃんとしたURLが入っていない。
imageDetail.jsonの中身が
{"ImageURI":"ay-s-react-practice-6:latest"}
となっており、"imageUri"がURIになっていないことが原因と思われた。
そこで、imageDetail.jsonを作っているbuildspec.yml
を修正した。
誤:元の記載) - printf '{"ImageURI":"%s"}' $IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json 正:修正後) - printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
おわりに
土日をつぶして2週間かかったが、CodePipelineでソースコードをデプロイすることができた。
その他参考資料
Lambda+API GatewayからDynamoDBにアクセスする方法
はじめに
今回の記事の目的
Lambda関数を作成し、DynamoDBにアクセスする。 Lambda関数を呼び出すためにAPI Gatewayを利用する。
IAMロールを作成する
「DynamoDBにアクセスする権限」を「Lambdaに」与える。
- IAMコンソールの「ロール」画面から「ロールの作成」。
- ユースケースの選択で「Lambda」を選択。
- Attach アクセス権限ポリシーで以下を選ぶ。
- AWSLambdaBasicExecutionRole
- AmazonDynamoDBFullAccess
以下の通り作成した。
API Gateway
Lambdaプロキシ統合
Lambdaプロキシ統合という仕組みを利用する。 HTTPSで受信したデータをLambda関数に自動的に渡したり、Lambda関数から戻ってきたデータを整形してHTTPSで戻す仕組み。
Lambda関数の定義
def lambda_handler(event , context)
- eventは、呼び出し元の情報。例えばAPI Gatewayだったら、ブラウザから送信された値を持つ。
- contextは、実行環境(Lambdaコンテナの環境)に関する情報。割り当てられたメモリなど。
Lambda関数の戻り値
Lambda関数からの戻り値は、API GatewayのLambdaプロキシ統合の場合決まっている。
{ 'statusCode':'200', 'headers':{ 'content-type':コンテンツタイプ }, 'body' : 本文データ }
Lambda関数とAPI Gatewayを作成する
- マネジメントコンソールから「Lambda」を選択
- 「関数」→「関数の作成」
設計図の選択
- 今回は「設計図から作成」を選択する。
AWSがもともと作っているサンプルをもとに作成する。
- 今回は「microservice-http-endpoint-python」を選ぶ。
Node.jsで作りたい場合は「microservice-http-endpoint」を選ぶ。
基本設定
以下の通り基本設定をした。IAMロールは先ほど作ったものを選択する。
API Gatewayの設定
以下の通りAPIの設定をした。
セキュリティは
- オープン
- JWT オーソライザーを作成
から選択する。今回は公開APIとするためオープンを選択した。
JWTオーソライザーについては割愛。
JWT オーソライザーを使用した HTTP API へのアクセスの制御 - Amazon API Gateway
Lambda関数のコード
今回pythonを利用して記載する。
boto3のインポート
AWSサービスを使うためのpythonモジュールboto3
をインポートして、DynamoDBを利用する。
import json import boto3 dynamodb = boto3.resource('dynamodb')
lambda関数の定義
lambda関数を定義する。
def lambda_handler(event, context):
※これ以下のコードはすべてlambda_handlerの中身なので、インデントを下げて記載する。
DynamoDBのテーブルから値を取得する。
keijitable = dynamodb.Table("ay-s-keijiban") # テーブルを変数に入れる response = keijitable.scan() # テーブルの全データを取得するメソッド。あまり大きいと全部読み取れないので注意。
HTMLの作成
返却したいHTMLを作成する。
html = "<html>・・・・・</html>"
注意:""
の中で"
を使いたい場合はエスケープする必要がある。(例:<meta charset=\"UTF-8\">
)
HTMLを作成する中で、取得したデータを利用する。
for data in sorted(response["Items"], reverse=True, key=lambda d: d["postat"]): ・・・・ ・・・・
for文の構文
for 変数名 in オブジェクト :
sortedの構文
sorted()
は、ソートした新たなリストを生成するメソッド。
sorted( 元のリスト, 引数)
引数reverse
は、True
とすると降順にソートする。
また、引数key
は、
key=lambda x: x[1]
とすることで、1番目の要素をキーとしてソートできる。
key=lambda x: x["postat"]
でpostat
項目を要素としてソートできる。
sortedの資料
[解決!Python]リスト(配列)をソートしたり、逆順にしたりするには(sort/reverseメソッド、sorted/reversed関数):解決!Python - @IT
返却項目作成
最後に返却するJSONを作成する。
return { 'statusCode':'200', 'headers':{ 'content-type':'text/html' }, 'body' : html }
pythonのポイント
以下の講座を参考にした。コードはコピペできないので重要要素だけメモ。
AWS Lambdaを活用したサーバレス実践 -第3回- - Schoo(スクー)
Lambda関数をテストする
「Test」→「Configure test event」を選択。
イベントテンプレートは「apigateway-aws-proxy」を選択。
要求電文は特に変更しない。
エラー発生 #1
以下のエラーが発生した。
[ERROR] JSONDecodeError: Expecting value: line 1 column 1 (char 0) Traceback (most recent call last): File "/var/task/lambda_function.py", line 39, in lambda_handler payload = event['queryStringParameters'] if operation == 'GET' else json.loads(event['body']) File "/var/lang/lib/python3.7/json/__init__.py", line 348, in loads return _default_decoder.decode(s) File "/var/lang/lib/python3.7/json/decoder.py", line 337, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "/var/lang/lib/python3.7/json/decoder.py", line 355, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None
原因は、デプロイをする前にテストしていたことである模様。
設計図にもともと記載があったコードを実行してしまっていたようだった。
エラー発生 #2
デプロイしてからテストしたところ、以下のエラーが発生。
START RequestId: XXXXXXXXXXXX Version: $LATEST [ERROR] KeyError: 'items' Traceback (most recent call last): File "/var/task/lambda_function.py", line 14, in lambda_handler for data in sorted(response["items"], reverse=True, key=lambda d: d["postat"]): END RequestId: XXXXXXXXXXXX REPORT RequestId: XXXXXXXXXXXX Duration: 72.25 ms Billed Duration: 73 ms Memory Size: 512 MB Max Memory Used: 69 MB Init Duration: 372.27 ms
原因は、以下のコードの Items
がitems
になっていたタイプミスだった。
for data in sorted(response["Items"], reverse=True, key=lambda d: d["postat"]):
Lambda関数のコードに書いているファイル名はlambda_function.py
となる。
CloudWatch logs
テスト実行結果からCloudWatch logsに遷移して、以下のようにログを見ることができる。
テスト成功
諸々修正してテストが成功した。
APIを打鍵してみる
API Gatewayのエンドポイントを確認して、打鍵してみる。
確認結果
APIをブラウザから打鍵できた。
おわりに
Lambda+API Gatewayを利用してAPIを作成する方法がわかった。 また、pythonからDynamoDBにアクセスする方法がわかった。
関連記事
前回までの記事
DynamoDBでテーブルを作成して、データを追加する。
AWS DynamoDBにデータベースを作成し、データを入力する方法メモ
はじめに
今回の記事の目的
AWS上にDynamoDBを作成し、レコード(アイテム)を追加する。 アイテムの追加は、マネジメントコンソールからの実施と、AWS CLIを用いた2つの方法で実施してみる。
DynamoDBとは
DBモデル
- AWSが提供するフルマネージド型のNo SQL データベース。
- キーバリュー型のDBである。
- 日付や時刻の型がない。
- レコード(アイテム)ごとに項目が異なっていても良い。
- Writeは結果整合性モデル(2つのAZに書き込みが終わったら完了扱い)
- Readは結果整合性モデルだが、オプションで強い整合性モデルも利用できる。
NW、性能
- 高可用性(3か所のAZ)
- プロビジョンドスループットといって、事前に必要なキャパシティを設定できる。テーブルごととCRUDごとに設定できる。(例:Read 100、Write 1000)
- VPCに属さない。
- ストレージの上限がない。
- 大量に発生する行動データなどに使われることが多い。
作成するテーブル
以下レイアウトでテーブルを作成する。
属性名 | 型 | 説明 | プライマリキー |
---|---|---|---|
id | Number | 投稿のID | ○ |
name | String | 投稿者名 | |
message | String | 投稿したメッセージ | |
postat | String | 投稿した日時 |
テーブルの中身は、以下のAWS Lambda講座で例として利用されていたものと基本的に同じ。
AWS Lambdaを活用したサーバレス実践 -第2回- - Schoo(スクー)
テーブルを作成する
AWSマネジメントコンソールから「DynamoDB」を検索し、「テーブルの作成」。
テーブル名設定
現在の AWS アカウントとリージョンで一意である必要がある。
キーの設定
テーブルのキーを設定する。キーにはいくつか種類がある。
パーティションキー
- テーブル作成時に、1つの属性データを選び、パーティションキーにする。
- ソートキーと合わせて、データを一意に特定する。
- パーティションキーごとに、DynamoDB内部でパーティション分けしてデータ管理している。
ソートキー
- テーブル作成時に、パーティションキーのほかに1つの属性データを選び、ソートキーにすることができる。
- パーティションキーと合わせて、データを一意に特定する。
- 同じパーティションキーを持つアイテムのグループでは、アイテムはソートキーを使ってソートされて管理されている。
ローカルセカンダリーインデックス(Local Secondary Index:LSI)
- ソートキー以外で絞込検索を行うインデックス。
- ソートキーを設定していないテーブルには利用できない。
- パーティションキーとソートキーが複合キーになっているテーブルで、パーティションキーとまた別の属性で検索したいとき、その別の属性をLSIにすることで、パーティションキー+LSIという検索ができる。
グローバルセカンダリーインデックス(Global Secondary Index:GSI)
イメージがわかりやすい参考資料
DynamoDBのキー・インデックスについてまとめてみた - Qiita
【参考】テーブル作成画面
アイテムを作成する
マネジメントコンソールで作成
マネジメントコンソールからアイテムを作成できる。
値を入力する。
AWS CLIで作成
AWS CLIの初期設定
以下の記事「AWS CLIの基本設定」を参照。
tomiko0404.hatenablog.com
アイテムの作成コマンド
put-item
メソッドを使う。
構文は以下。
$ put-item --table-name <value> --item <value> [--expected <value>] [--return-values <value>] [--return-consumed-capacity <value>] [--return-item-collection-metrics <value>] [--conditional-operator <value>] [--condition-expression <value>] [--expression-attribute-names <value>] [--expression-attribute-values <value>] [--cli-input-json | --cli-input-yaml] [--generate-cli-skeleton <value>]
公式リファレンス
put-item — AWS CLI 2.2.44 Command Reference
itemオプションに設定するデータ形式
item
オプションに実際の値を入れていくが、以下のような形で、通常のJSONを設定してもデータ投入できない。
# NG例 { "id": 2, "name": "CLI子2丁目", "message": "AWSCLIからput-item。JSONの中に改行の\\を入れると良くないらしい。", "postat": "2021-10-09 4:00" }
DynamoDB特有のJSON構文で記載する必要がある。
# OK例 { "id": { "N": "2" }, "name": { "S": "CLI子2丁目" }, "message": { "S": "AWSCLIからput-item。JSONの中に改行の\\を入れると良くないらしい。" }, "postat": { "S": "2021-10-09 4:00" } }
このJSONの構文としては、
"項目名" : { "データ型" : "実際の値"}
となる。データ型は、
- N :数値
- S :文字列
- B :バイナリ
- SS :文字列配列
- NS :数字配列
など。
データ型が「数値」であっても、JSON上は""
で囲む点に注意。
詳細は公式ドキュメント参照。
put-item — AWS CLI 2.2.44 Command Reference
各データ型の説明:
命名ルールおよびデータ型 - Amazon DynamoDB
最終的にコマンドラインで実行するのは以下のコマンド。
$ aws dynamodb put-item \ --table-name ay-s-keijiban \ --item '{ "id": { "N": "2" }, "name": { "S": "CLI子2丁目" }, "message": { "S": "AWSCLIからput-item。JSONの中に改行の\\を入れると良くないらしい。" }, "postat": { "S": "2021-10-09 4:00" } }' \ --return-consumed-capacity TOTAL
\
はコマンド中での改行を意味する(Linux一般)。
注意:JSON中に\
を記載するとうまくいかない。
公式ドキュメントだとJson中にも\
を書いているようなのだが。
また、--return-consumed-capacity TOTAL
は
消費された書き込み容量単位の総数を返します。
とのこと。コマンドの応答として以下が返却された。
{ "ConsumedCapacity": { "TableName": "ay-s-keijiban", "CapacityUnits": 1.0 } }
アイテムを追加した結果
GUI、CLIの2つの方法でアイテムを追加することができた。
ちなみに、マネジメントコンソールで作成したデータは、その後の手順で同じid
となるデータを上書きしてしまい、消えてしまっている。
おわりに
DynamoDBのテーブルを作成し、アイテムを追加することができた。