AWSのECSを構築するその1(ネットワーク構築)
完成予定
今回
今回やること
VPCの作成
コンソールよりVPCの作成を押下
VPCのパラメータを設定
VPC作成完了
サブネットを作成
サブネットを作成を押下
パラメータを設定
サブネットが作成されたことを確認
ルートテーブルの作成
ルートテーブルの作成を選択
パブリック用のルートテーブルを作成
パブリック用のサブネットの関連付けを選択
パブリック用のサブネットを関連づけ
↓
プライベート用のルートテーブルを作成
プライベート用のサブネットの関連付けを選択
プライベート用のサブネットを関連づけ
↓
インターネットゲートウェイの作成
インターネットゲートウェイの作成を選択する
パラメータを設定
VPCへアタッチを選択する
VPCを指定してアタッチ
パブリック用のルートテーブルのルートを編集する
インターネットゲートウェイを割り当て
関連記事
m-shige1979.hatenablog.com m-shige1979.hatenablog.com m-shige1979.hatenablog.com m-shige1979.hatenablog.com
EC2で出力するログをcloudwatchlogsに連携する
やりたいこと
nginxやバックエンドのアプリログをcloudwatchlogsへ流す
参考にしたやつ
【AWS】Amazon CloudWatch Logs でログ収集をやってみた|コラム|クラウドソリューション|サービス|法人のお客さま|NTT東日本
クイックスタート: 実行中の EC2 Linux インスタンスに CloudWatch Logs エージェントをインストールして設定する - Amazon CloudWatch Logs
流れ
- EC2インスタンスを起動
- EC2インスタンスにnginxを入れてログの出力を確認する
- cloudwatchへの出力用ポリシーを作成する
- IAMロールを作成し、3で作成したポリシーを割り当て
- EC2にロールを設定する
- awslogsのインストール
1.EC2インスタンスを起動
こんな感じ
2. EC2インスタンスにnginxを入れてログの出力を確認する
nginxのインストール
$ sudo yum update -y $ sudo amazon-linux-extras install nginx1 -y $ nginx -v nginx version: nginx/1.20.0 $ sudo systemctl start nginx $ sudo systemctl enable nginx Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service. $
ブラウザで確認
ログファイルを確認
/etc/nginx/conf.d/default.conf
server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }
再起動
$ sudo systemctl restart nginx $
↓
$ sudo ls -la /var/log/nginx/ 合計 8 drwx------ 2 root root 41 12月 11 14:33 . drwxr-xr-x 8 root root 333 12月 11 14:32 .. -rw-r--r-- 1 root root 1075 12月 11 14:46 access.log -rw-r--r-- 1 root root 604 12月 11 14:45 error.log $
3. cloudwatchへの出力用ポリシーを作成する
①ポリシーを作成を選択
②cloudwatchlog用のアクションを選択
タグをつける
ポリシー名をつけて作成
4. IAMロールを作成し、3で作成したポリシーを割り当て
↓ ↓ ↓ ↓
5. EC2にロールを設定する
↓
6. awslogsのインストール
①yum install
$ sudo yum install awslogs -y
②backup
$ sudo cp -p /etc/awslogs/awscli.conf /etc/awslogs/awscli.conf.org $ sudo cp -p /etc/awslogs/awslogs.conf /etc/awslogs/awslogs.conf.org
③/etc/awslogs/awscli.confを編集
※東京リージョンに変更
[plugins] cwlogs = cwlogs [default] region = ap-northeast-1
④/etc/awslogs/awslogs.conf
※末尾に追加
[HttpdAccessLog] datetime_format = %d/%b/%Y:%H/%M/%S file = /var/log/nginx/access.log buffer_duration = 5000 log_stream_name = {instance_id} initial_position = start_of_file log_group_name = /HttpdAccessLog [HttpdErrorLog] datetime_format = %d/%b/%Y:%H/%M/%S file = /var/log/nginx/error.log buffer_duration = 5000 log_stream_name = {instance_id} initial_position = start_of_file log_group_name = /HttpdErrorLog
⑤awslogs起動、自動起動設定
sudo systemctl start awslogsd.service sudo systemctl enable awslogsd.service
↓
できた
所感
手順はそんなに難しくない ec2のログを手間なく送信したい場合は使えると思われる。 そういえばローテートされたらどうなるのかな・・・
PrismaでDBのhasOneを試す
前の記事
今回
外部キーの定義って
後付けや変更ができないのかな?
普段SQLを使うことが多かったので今回prisma migrate dev
でエラーでできないから分かっていない・・・
設定したもの
prisma/schema.prisma
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } // ER図作成用 generator erd { provider = "prisma-erd-generator" output = "../doc/ERD.md" } // ユーザーデータ管理用 model User { // ID id Int @id @default(autoincrement()) // メールアドレス email String @unique // 氏名 name String? // 年齢 age Int? // 登録日時 createdAt DateTime @default(now()) @map("created_at") // 更新日時 updatedAt DateTime @default(now()) @map("updated_at") // プロフィール(One to One) profile Profile? // 記事(One to Many) posts Post[] // テーブルの物理名 @@map("users") } // プロフィール管理用 model Profile { // ID id Int @id @default(autoincrement()) // ニックネーム nickName String @map("nick_name") // 登録日時 createdAt DateTime @default(now()) @map("created_at") // 更新日時 updatedAt DateTime @default(now()) @map("updated_at") // 親テーブルの関連づけ userId Int @unique user User @relation(fields: [userId], references: [id]) // テーブルの物理名 @@map("profile") } // 記事管理用 model Post { // ID id Int @id @default(autoincrement()) // タイトル title String @map("title") // 登録日時 createdAt DateTime @default(now()) @map("created_at") // 更新日時 updatedAt DateTime @default(now()) @map("updated_at") // 親テーブルの関連づけ userId Int user User @relation(fields: [userId], references: [id]) // テーブルの物理名 @@map("posts") }
migrate後のCREATE文
CREATE TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `age` int DEFAULT NULL, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (`id`), UNIQUE KEY `users_email_key` (`email`) ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `posts` ( `id` int NOT NULL AUTO_INCREMENT, `title` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `userId` int NOT NULL, PRIMARY KEY (`id`), KEY `posts_userId_fkey` (`userId`), CONSTRAINT `posts_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
データ取得方法
基本は前回と同じ、今回は1対多のため、関連テーブルの条件絞り込みが可能
const user3 = await prisma.user.findFirst({ where: { name: 'hoge1' }, include: { profile: { select: { nickName: true, createdAt: true, }, }, posts: { select: { title: true }, where: { title: "test1" } } }, }) console.log("case3: ", user3)
↓
配下を全件取得した場合
id: 16, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, profile: { id: 5, nickName: 'aaaa', createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, userId: 16 }, posts: [ { id: 1, title: 'test1', createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, userId: 16 }, { id: 2, title: 'test2', createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, userId: 16 }, { id: 3, title: 'test3', createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, userId: 16 } ] }
一部のみ取得した場合
case3: { id: 16, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T09:58:03.729Z, updatedAt: 2021-11-23T09:58:03.729Z, profile: { nickName: 'aaaa', createdAt: 2021-11-23T09:58:03.729Z }, posts: [ { title: 'test1' } ] }
所感
ある程度のことはやってくれるイメージ
外部キーのこともあるから頻繁にデータ構造が変わる場合や複雑なSQLがある場合は不向き
次は多対多かgroup by
辺りを見てみる
できたもの
PrismaでDBのhasOneを試す
Prisma
hasOneについて
2つのテーブルが1対1、または0の関係のリレーショナル構造のこと
イメージ
Prismaでの定義の方法
prisma/schema.prisma
// ユーザーデータ管理用 model User { // ID id Int @id @default(autoincrement()) // メールアドレス email String @unique // 氏名 name String? // 年齢 age Int? // 登録日時 createdAt DateTime @default(now()) @map("created_at") // 更新日時 updatedAt DateTime @default(now()) @map("updated_at") // プロフィール(One to One) profile Profile? // テーブルの物理名 @@map("users") } // プロフィール管理用 model Profile { // ID id Int @id @default(autoincrement()) // ニックネーム nickName String @map("nick_name") // 登録日時 createdAt DateTime @default(now()) @map("created_at") // 更新日時 updatedAt DateTime @default(now()) @map("updated_at") // 親テーブルの関連づけ userId Int @unique user User @relation(fields: [userId], references: [id]) // テーブルの物理名 @@map("profile") }
親モデルへの追加定義
// プロフィール(One to One)
profile Profile?
これを追加することで関連するモデルが1または0の状態となる
子モデルへの追加定義
// 親テーブルの関連づけ userId Int @unique user User @relation(fields: [userId], references: [id])
紐付け用のカラムを追加し親と関連づける
マイグレーション後のCREATE文
CREATE TABLE `users` ( `id` int NOT NULL AUTO_INCREMENT, `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `age` int DEFAULT NULL, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (`id`), UNIQUE KEY `users_email_key` (`email`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `profile` ( `id` int NOT NULL AUTO_INCREMENT, `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `userId` int NOT NULL, `nick_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `profile_userId_key` (`userId`), CONSTRAINT `profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
データ処理
親のみ取得
const user1 = await prisma.user.findFirst({ where: { name: 'hoge1' }, }) console.log("case1: ", user1)
親子のデータを同時に取得
const user2 = await prisma.user.findFirst({ where: { name: 'hoge1' }, include: { profile: true, }, }) console.log("case2: ", user2)
子のデータを一部のみ取得
const user3 = await prisma.user.findFirst({ where: { name: 'hoge1' }, include: { profile: { select: { nickName: true, createdAt: true, }, }, }, }) console.log("case3: ", user3)
子のデータより親を引き出す
const profile1 = prisma.profile.findFirst({ where: { nickName: 'aaaa' }, }); const user4 = await profile1.user(); profile1.then((result) => { console.log("case4: ", result, user4); })
↓
case1: { id: 10, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z } case2: { id: 10, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z, profile: { id: 3, nickName: 'aaaa', createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z, userId: 10 } } case3: { id: 10, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z, profile: { nickName: 'aaaa', createdAt: 2021-11-23T01:14:46.450Z } } case4: { id: 3, nickName: 'aaaa', createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z, userId: 10 } { id: 10, email: 'hoge1@example.com', name: 'hoge1', age: 20, createdAt: 2021-11-23T01:14:46.450Z, updatedAt: 2021-11-23T01:14:46.450Z }
親だけ削除とかはできんかった
多分、リレーションの設定で削除も連動するようにしてないせいと思うが seedファイルを改修する際に、削除⇨作成したらエラーになる。 ちょっと気をつけよう
できたやつ
参考
ExpressとPrismaでREST API実装1
前々回
前回
今回は
NodeJSのフレームワークのExpress
に Prisma
を使用したREST APIを実装
今回は触りだけなのでシンプルな構成
参考資料
これ
src/index.ts
import express from 'express' import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() // express server const app = express() // リクエストデータをjsonで受け取れるようにする app.use(express.json()) app.use(express.urlencoded({ extended: true })); // ユーザー一覧 app.get('/users', async (req, res) => { // ユーザー一覧を取得 const users = await prisma.user.findMany({}) // 返却 res.json(users) }) // ユーザー詳細 app.get('/user/:id', async (req, res) => { // パラメータを取得 const { id } = req.params; // ユーザー情報を取得 const user = await prisma.user.findFirst({ where: { id: Number(id) } }) // 返却 res.json(user) }) // ユーザー登録 app.post('/user', async (req, res) => { // パラメータ取得 console.log(req.body); const { email, name } = req.body; // この辺でバリデーション // 登録 const user = await prisma.user.create({ data: { "email": email, "name": name } }) // 返却 res.json({ "status": 0, "result": user }) }) // ユーザー更新 app.put('/user/:id', async (req, res) => { // パラメータ取得 console.log(req.body); const { id } = req.params; const { email, name } = req.body; // この辺でバリデーション // 登録 const user = await prisma.user.update({ where: { "id": Number(id) }, data: { "email": email, "name": name, "updatedAt": new Date() } }) // 返却 res.json({ "status": 0, "result": user }) }) // ユーザー削除 app.delete('/user/:id', async (req, res) => { // パラメータを取得 const { id } = req.params; // ユーザー情報を削除 const user = await prisma.user.deleteMany({ where: { id: Number(id) } }) // 返却 // 返却 res.json({ "status": 0, "result": user }) }) // server listen const server = app.listen(3000)
prismaオブジェクト配下から各モデルを引っ張ってくる
リレーションでhasOne
やhasMany
の構成をしていてついでに取得とかもやってくれるよう
今回はデータ考えてなかったので実装はしていないです。
VSCodeで書くとエラーが検知しやすい
Typescriptの拡張かPrismaの拡張か判断に困ることはありますが、 「そんな項目ないよ」的な指摘をピンポイントで出してくれるので 誤りに早く気づく
ts-node-dev
を利用したホットリロード
なんか初回しか検知しないのでおかしいと思って起動時のパラメータを追加 最終的にはこんな感じ
package.json
"scripts": { "start": "ts-node-dev --respawn --debug --exit-child src/index.ts", },
今回のもの
PrismaでSeedでデータを投入する
前回
Prisma?
今回
Seedでデータを投入する
やり方
なんかprisma/seed.ts
を作成して、データを登録する処理を書くみたい
やってみる
プロジェクト構成は前回のまま
package.json
{ "name": "app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@prisma/client": "^3.5.0", "@types/express": "^4.17.13", "@types/node": "^16.11.9", "express": "^4.17.1", "ts-node": "^10.4.0", "typescript": "^4.5.2" }, "devDependencies": { "prisma": "^3.5.0" } }
スクリプト作成
参考になりそうなもの
prisma/seed.ts
import { PrismaClient, Prisma } from '@prisma/client' const prisma = new PrismaClient() // モデル投入用のデータ定義 const userData: Prisma.UserCreateInput[] = [ { name: 'hoge1', email: 'hoge1@example.com', }, { name: 'hoge2', email: 'hoge2@example.com', }, { name: 'hoge3', email: 'hoge3@example.com', }, ] const transfer = async () => { const users = []; for (const u of userData) { const user = prisma.user.create({ data: u, }) users.push(user); } return await prisma.$transaction(users); } // 定義されたデータを実際のモデルへ登録する処理 const main = async () => { console.log(`Start seeding ...`) await transfer(); console.log(`Seeding finished.`) } // 処理開始 main() .catch((e) => { console.error(e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })
seedコマンドを追加
package.json
以下を追加
"prisma": { "seed": "ts-node prisma/seed.ts" }
実行
app# npx prisma db seed Environment variables loaded from .env Running seed command `ts-node prisma/seed.ts` ... Start seeding ... Seeding finished. 🌱 The seed command has been executed. app#
↓
OK
所感
seed.ts
に共通処理を記載して、あとはデータやテーブルは別ファイルで管理すればそこまで肥大化しないのでは
と思っている。
次はそうだな…expressと連携してAPI経由でデータを取得するところをやってみよう。
github
Typescriptで動作するDBのマイグレーションツールPrismaを触ってみた
Prisma
いくつかマイグレーションツール探していてなんか良いのがなかった
最悪、sqlでどうにかしようかなぁと思っていた。
お試し環境
node16のdockerコンテナ
docker-compose
version: "3" services: # backend backedn: # コンテナ名 container_name: node-dev # build image: node:16 # コンテナの中に入る tty: true # 他のコンテナ起動後に起動するように制御 depends_on: - db # ボリューム volumes: - "./app" # WORKDIR working_dir: /app # 環境変数 environment: TZ: "Asia/Tokyo" # DBサーバ db: # コンテナ名 container_name: db # build image: mysql # 環境設定 environment: TZ: Asia/Tokyo MYSQL_ROOT_USER: root MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: sample1 MYSQL_USER: app MYSQL_PASSWORD: password # コンテナの中に入る tty: true # ボリューム volumes: - mysql_data:/var/lib/mysql # ポート開放 ports: - 3306:3306 # 名前付きボリュームをdockerホストの管理下で作成 volumes: # mysql_data: {}
build, up
docker-compose build docker-compose run --rm backend bash
↓
# node -v v16.13.0 # npm -v 8.1.0 #
手順
プロジェクト初期化
# npm init -y Wrote to /app/package.json: { "name": "app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } #
関連パッケージをインストール
npm install --save typescript npm install --save @types/node npm install --save ts-node npx tsc --init npm install --save express npm install --save @types/express npm install --save-dev prisma npm install --save @prisma/client
↓
# cat package.json { "name": "app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@prisma/client": "^3.5.0", "@types/express": "^4.17.13", "@types/node": "^16.11.9", "express": "^4.17.1", "ts-node": "^10.4.0", "typescript": "^4.5.2" }, "devDependencies": { "prisma": "^3.5.0" } } #
prismaの初期化
npx prisma init
↓
. |-- package-lock.json |-- package.json |-- prisma | `-- schema.prisma `-- tsconfig.json
.env
、prisma/schema.prisma
が生成されるため、DBの情報を修正する
.env
DATABASE_URL="mysql://root:password@db:3306/sample1"
prisma/schema.prisma
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") }
モデルを作成する
prisma/schema.prisma
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) email String @unique name String? @@map("users") }
マイグレーション実行
npx prisma migrate dev --name init
↓
新規の場合はmigrations
ディレクトリが作成され、SQLファイルができる
migrations/ └─ 20211120075456_init/ └─ migration.sql
DB構成
適当なデータを準備
INSERT INTO users ( `email`, `name` ) VALUES ('test1@test.com', 'test1'), ('test2@test.com', 'test2'), ('test3@test.com', 'test3') ;
サンプルコード
index.ts
import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { const allUsers = await prisma.user.findMany({}) console.dir(allUsers, { depth: null }) } main() .catch((e) => { throw e }) .finally(async () => { await prisma.$disconnect() })
↓
npx ts-node index.ts
↓
[ { id: 1, email: 'test1@test.com', name: 'test1' }, { id: 2, email: 'test2@test.com', name: 'test2' }, { id: 3, email: 'test3@test.com', name: 'test3' } ]
うん、いい感じ
所感
最初はSequelize
でいく予定でしたがテーブル名を別名で定義するところとかがうまくできなくて
困っていました。
こちらは結構わかりやすい感じ
ゴリゴリ対応は最悪SQL直がきにしかないかもですがシンプルな構成の場合はやりやすいかも
次はSeedを試す
参考
Prisma - Next-generation Node.js and TypeScript ORM for Databases