how-to-trigger-lambda-from-kinesis

How to Trigger an AWS Lambda from Kinesis

How to Trigger an AWS Lambda from Kinesis

先建立資料夾拉~~~

mkdir how-to-trigger-lambda-from-kinesis
cd how-to-trigger-lambda-from-kinesis
npx cdk init app --language typescript

安裝 kinesis & lambda

npm i @aws-cdk/aws-kinesis
npm i @aws-cdk/aws-lambda
npm i @aws-cdk/aws-lambda-event-sources

新增一個檔案.env

touch .env

1
2
3
4
AWS_ACCOUNT_ID=1234567890123
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=xxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxx

修改 how-to-trigger-lambda-from-kinesis.ts

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { HowToTriggerLambdaFromKinesisStack } from '../lib/how-to-trigger-lambda-from-kinesis-stack';

const app = new cdk.App();
new HowToTriggerLambdaFromKinesisStack(app, 'HowToTriggerLambdaFromKinesisStack', {
env: {
account: process.env.AWS_ACCOUNT_ID,
region: process.env.AWS_REGION
},
});

修改 how-to-trigger-lambda-from-kinesis-stack.ts

1
2
3
4
5
6
7
8
9
10
11
12
import * as cdk from '@aws-cdk/core';
import * as kinesis from '@aws-cdk/aws-kinesis';

export class HowToTriggerLambdaFromKinesisStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const stream = new kinesis.Stream(this, 'MyKinesisStream', {
streamName: 'MyKinesisStream',
});
}
}`

新增 src/index.js 撰寫一下lambda code (這次來用一下nodejs寫)

1
2
3
4
5
exports.handler = async (event) => {
event.Records.forEach((record) => {
console.log('Record: %j', record);
});
};

準備發佈前

cdk bootstrap
⏳ Bootstrapping environment aws://xxxxxxxx/us-east-2…
✅ Environment aws://xxxxxxx/us-east-2 bootstrapped (no changes).

發佈

cdk deploy

test

aws kinesis list-streams
aws kinesis put-record --data "aGVsbG8sIHdvcmxk" --stream-name MyKinesisStream --partition-key pk1

label

code

github

gitlab CI/CD introduce

Gitlab CI/CD

軟件開發的持續方法基於自動執行腳本,以最大程度地減少在開發應用程序時引入錯誤的機會。從開發新代碼到部署新代碼,他們幾乎不需要人工干預,甚至根本不需要干預。
它涉及到在每次小的迭代中就不斷地構建、測試和部署代碼更改,從而減少了基於已經存在 bug 或失敗的先前版本開發新代碼的機會。

  • Continuous Integration(持續集成),假設一個應用程序,其代碼存儲在 GitLab 的 Git 倉庫中。開發人員每天都要多次推送代碼更改。對於每次向倉庫的推送,你都可以創建一組腳本來自動構建和測試你的應用程序,從而減少了嚮應用程序引入錯誤的機會。這種做法稱爲持續集成,對於提交給應用程序(甚至是開發分支)的每項更改,它都會自動連續進行構建和測試,以確保所引入的更改通過你爲應用程序建立的所有測試,準則和代碼合規性標準。

  • Continuous Delivery(持續交付),持續交付是超越持續集成的更進一步的操作。應用程序不僅會在推送到代碼庫的每次代碼更改時進行構建和測試,而且,儘管部署是手動觸發的,但作爲一個附加步驟,它也可以連續部署。此方法可確保自動檢查代碼,但需要人工干預才能從策略上手動觸發以必輸此次變更。

  • Continuous Deployment(持續部署),與持續交付類似,但不同之處在於,你無需將其手動部署,而是將其設置爲自動部署。完全不需要人工干預即可部署你的應用程序。

GitLab CI/CD 是如何工作的

爲了使用 GitLab CI/CD,你需要一個託管在 GitLab 上的應用程序代碼庫,並且在根目錄中的 .gitlab-ci.yml 文件中指定構建、測試和部署的腳本。

在這個文件中,你可以定義要運行的腳本,定義包含的依賴項,選擇要按順序運行的命令和要並行運行的命令,定義要在何處部署應用程序,以及指定是否 要自動運行腳本或手動觸發腳本。

爲了可視化處理過程,假設添加到配置文件中的所有腳本與在計算機的終端上運行的命令相同。

一旦你已經添加了. gitlab-ci.yml 到倉庫中,GitLab 將檢測到該文件,並使用名爲 GitLab Runner 的工具運行你的腳本。該工具的操作與終端類似。

這些腳本被分組到 jobs,它們共同組成一個 Pipeline。一個最簡單的 .gitlab-ci.yml 文件可能是這樣的:

1
2
3
4
5
6
before_script:   
- apt-get install zip -y

run-test:
script:
- zip --version

了解keyword

因此你要撰寫GitLab前,需要先了解許多keyword,可以到官網中,GitLab CI/CD Pipeline Configuration Reference,下面就稍微介紹一下一些常用的keyword吧!

Keyword Description
Pipeline 線程,每次執行CICD都是一個線程
stages 線程中定義你的步驟,可以多個步驟,他是有順序的
job 每個階段也都會有許多job
variables 定義你的變數,在你的yaml檔案中,均可以取用這變數值
image 你的docker image
before_script 跑你的image前所執行的指令
script 跑你的image時執行的指令
after_script 跑你的image後所執行的指令
artifacts 跑完Job成功後附加到作業的文件和目錄列表
dependencies 需要先跑完哪個stages,才能被執行

來個範例吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 宣告 Pipeline 有哪些 stages,並排定他們的先後順序
stages:
- build:golang
- build:aws-cli
- build:node

# 宣告變數
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_REGION: $AWS_DEFAULT_REGION

# 首先宣告 job 的名稱
build:golang:
stage: build:golang
image: golang:1.18.1
before_script:
- apt-get update -q && apt-get install -y zip
script:
- cd ./fs && go mod tidy && make build
- cd ../iam && go mod tidy && make build
cache:
untracked: true
# 在這目錄下,執行後的檔案都會被打包起來
artifacts:
paths:
- ./
# 限制只有 develop branch 會執行此 job
only:
- master

build:aws-cli:
stage: build:aws-cli
image: amazon/aws-cli
script:
- aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
- aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
- aws configure set region $AWS_DEFAULT_REGION
cache:
untracked: true
artifacts:
paths:
- ./
only:
- master
dependencies:
- build:golang

build:node:
stage: build:node
image: node:16
script:
- npm install -g aws-cdk@1.171.0
- npm install
- cdk bootstrap aws://$AWS_ACCOUNT_ID/$AWS_DEFAULT_REGION
cache:
untracked: true
artifacts:
paths:
- ./
only:
- master
dependencies:
- build:aws-cli

看的圖吧

  • 當你push你的code上到master時,pipeline的stage名稱
    label
  • 你可以查看你跑的Job
    label
  • 你可以查看你跑的過程log
    label

結論

gitlab其實還有很多功能,這部分真的需要許多實戰經驗,畢竟部署時,會隨著開發所需要的環境與依賴而增加難易度,建議多看看官方文件,gitlab還有runner的用法,還有本文中也尚未提到變數的設定,這些之後再說明了。

How to using CDK

什麼是CDK

CDK 是 AWS Cloud Development Kit (AWS CDK) 是一套開源軟體開發架構,使用熟悉的程式設計語言定義您的雲端應用程式資源。

早期佈建雲端應用程式可能是具有挑戰性的過程,需要透過手動操作、撰寫自訂指令碼、維護範本或學習特定領域的語言。AWS CDK 使用程式設計語言的熟悉性和表達能力,幫助應用程式進行模型分析。

它提供的高階元件 (稱為建構) 可利用經過驗證的預設值預先設定雲端資源,因此,您可以輕鬆建置雲端應用程式。AWS CDK 透過 AWS CloudFormation,以安全、可重複的方式佈建您的資源。它也讓您能夠編寫和分享自己的自訂建構,以整合組織的需求,協助您加快新專案。

CDK 支援哪些語言

目前CDK已經出到V2的版本,透過doc能夠知道,CDK支援了Python、.Net、Go、Java、TypeScript,從這點來看,比較起Terraform 的自定義語言,CDK提供了不同的撰寫語言,讓開發者使用自己所熟悉的語言撰寫IaC,這點真是大大的降低學習成本,那使用Terraform難道就活該倒楣嗎?不~針對偏好使用 Terraform 的客戶,cdktf 提供以 TypeScript 和 Python 定義 Terraform HCL 狀態檔案的 CDK 建構。

CDK8s

對於 Kubernetes 使用者,cdk8s 專案可讓您使用 CDK 建構在 TypeScript、Python 和 Java 中定義 Kubernetes 組態。超級方便~

一切都從官網開始

第一次學習CDK的人可以使用以下兩個官網,稍微看看,了解CDK能夠做到哪些事情
cdk doc
cdkf

需求

小編這次因內部需求所需,需將AWS Support 所開立的CASE同步到自己的內部系統上,因此使用CDK作為IaC的主要工具,並以golang作為lambda的語言,以下為本次實作架構,另外,小編使用的電腦為MAC,如果是Windows就抱歉拉,如果需要Windows,歡迎敲碗~

架構

建立CDK專案前的準備

  1. 安裝 Node 和 NPM, 若你的安裝或升級過程遇到權限問題, 可能會用到sudo, 需要鍵入 superuser password
    $ brew install node
    $ npm install -g aws-cdk

  2. 安裝 golang
    $ brew install go

  3. 安裝AWS CLI
    下載CLI V2

  4. 生成Access Key ID & Secret Access Key,建議大家可以建立一個IAM 的 group,然後把新創建的user加入群組

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #! /bin/bash

    #VARs
    username=$1
    group="admin-group"
    password=`openssl rand -base64 32`

    #create user with password then add user into a group and create ak and sk
    echo "User: $username Password: $password"
    aws iam create-user --user-name $username --tags Key=Org,Value=Support Key=CreatedBy,Value=awscli
    aws iam create-login-profile --user-name $1 --password $password --password-reset-required
    aws iam add-user-to-group --user-name $username --group-name $group
    aws iam create-access-key --user-name $username
  5. 設定 AWS CLI
    $ aws configure

    Access Key ID [None]: ANOTREALACCESSKEYID
    1
    2
    3
    AWS Secret Access Key [None]: ANOTREALSECRETACCESSKEY
    Default region name [None]: eu-east-1
    Default output format [None]: json
  6. 進行CDK初始化, 需要先得知”Account”跟”Region”的資訊, 從上一個步驟你知道了”Region”, Account的資訊由下面的aws sts get-caller-identity指令得到, Account 的數值是由一串數字組成.
    $ aws sts get-caller-identity
    $ cdk bootstrap aws://[ACCOUNT-NUMBER]/[REGION]

    1
    2
    3
    4
    ⏳ Bootstrapping environment aws://123456789012/us-east-1...
    CDKToolkit: creating CloudFormation changeset...

    ✅ Environment aws://123456789012/us-east-1 bootstrapped.

‘—cloudformation-execution-policies’ 是允許cloudformation可以執行時使用的IAM policy, 根據the priciple of least privilege, AWS 建議用戶可以另外指定, 上述的做法會以目前用戶擁有的權限給cloudformation 使用.

記得替換參數,指令下完後,你會看到正在初始化專案~

上方的軟體安裝均可以使用Docker,不過如果你是初學,建議直接安裝於MAC身上,看這人習慣,這邊就不贅述,小編我是直接安裝MAC上,方便開發。AWS的key也可以手動生成,看個人習慣。

安裝建立CDK專案

準備好以上就可以透過指令產生CDK預設專案拉~

$ mkdir cdk-demo
$ cd cdk-demo
$ cdk init --language typescript

├── bin
│   └── cdk-demo.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-demo-stack.ts
├── package.json
├── package-lock.json
├── README.md
├── test
│   └── cdk-demo.test.ts
└── tsconfig.json

以下是一些重要檔案及其用途:

  • bin/cdk-project.ts - 這是進入 CDK 應用程式的途徑。此檔案將會載入/建立我們在 lib/* 底下定義的所有堆疊

  • lib/cdk-project-stack.ts - 這是主要的 CDK 應用程式堆疊的定義之處。資源及其屬性可存放於此處。

  • package.json - 您會在此處定義專案相依性,以及一些額外資訊和建置指令碼 (npm build、npm test、npm watch)。

  • cdk.json - 此檔案會向工具組指出如何執行你的應用程式,以及與 CDK 和你的專案相關的一些額外設定和參數。

我們將著重於 lib/cdk-demo-stack.ts 和 bin/cdk-demo.ts 檔案,以建立我們的基礎設施。我們將新增一段程式碼。

修改 bin/cdk-demo.ts

使其顯示如下。請務必將您的 AWS 帳戶 ID 取代為正確的號碼,並選擇正確的區域。

1
2
3
4
5
6
7
8
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { CdkDemoStack } from '../lib/cdk-demo-stack';

const app = new cdk.App();
new CdkDemoStack(app, 'CdkDemoStack', {
env: { account: '123456789012', region: 'eu-west-1' },
});

接著修改 lib/cdk-demo-stack.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as iam from '@aws-cdk/aws-iam';
import * as events from '@aws-cdk/aws-events';
import * as logs from '@aws-cdk/aws-logs';
import * as targets from '@aws-cdk/aws-events-targets';
import * as path from 'path';
import * as assets from '@aws-cdk/aws-s3-assets';

export class CdkDemoStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Policy
const supportPolicy = new iam.PolicyStatement({
actions: ['support:*'],
resources: ['*'],
})

// lambda
const demoLambdaAsset = new assets.Asset(this, "GoServerLambdaFnZip", {
path: path.join(__dirname, "../lambda"),
})

// seting lambda and env
const demoLambda = new lambda.Function(this, 'msp', {
runtime: lambda.Runtime.GO_1_X,
timeout: cdk.Duration.seconds(300),
handler: "main",
code: lambda.Code.fromBucket(
demoLambdaAsset.bucket,
demoLambdaAsset.s3ObjectKey
),
environment: {
region: cdk.Stack.of(this).region,
zones: JSON.stringify(cdk.Stack.of(this).availabilityZones),
endpoint: 'endpoint',
},
})

// add policy
mspLambda.role?.attachInlinePolicy(
new iam.Policy(this, 'demoSupportPolicy', {
statements: [supportPolicy],
}),
)

// cloudwatch log group
const logGroup = new logs.LogGroup(this, 'demoLogGroup', {
logGroupName: 'demoLogGroup',
})

// add eventBridge
const demoEventRule = new events.Rule(this, 'demoRule', {
eventPattern: {
source: ["aws.support"],
},
})

// add lambda for eventBridge's target
demoEventRule.addTarget(new targets.CloudWatchLogGroup(logGroup))
demoEventRule.addTarget(new targets.LambdaFunction(demoLambda))
}
}

如果遇到import時找不到套件,記得安裝一下,例如 $ npm install @aws-cdk/aws-events

撰寫lambda

  1. 建立一個資料夾
    $ mkdir lambda

  2. 建立main.go
    $ touch main.go

  3. 寫code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

package main

import (
"context"
"fmt"
"log"
"os"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/joho/godotenv"
)

func main() {
lambda.Start(HandleRequest)
}

func HandleRequest(_ context.Context, event events.CloudWatchEvent) error {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
region := os.Getenv("region")
zones := os.Getenv("zones")
iam_endpoint := os.Getenv("iam_endpoint")

fmt.Println("env region:", region)
fmt.Println("env zones:", zones)
fmt.Println("env endpoint:", iam_endpoint)

// 接收到值後,寫你想要做的事情
fmt.Printf("PRINT: %#v\n", event)
return nil
}
  1. 一開始需要進行初始化並且載入相關套件
    $ go mod init main && go mod tidy

這邊golang的code單純進行print cloudwatch的值,且該lambda已經獲得aws.support的權限了,至於要做怎樣的操作,可自由發揮

開始部署嚕

  • 確認您的程式碼是否有效
    $ npm run build

  • 確認最終產生的CloudFormation是否正確
    $ cdk sync

    沒意外你會看到一堆很噁心的CloudFormation,你就會知道CDK其實只是幫你產生CloudFormation的yaml來執行而已~

  • 部署
    $ cdk deploy

    deploying...
    1
    2
    3
    4
    5
    6
    7
    CdkDemoStack: creating CloudFormation changeset...
    [··························································] (0/13)

    3:50:17 PM | CREATE_IN_PROGRESS | AWS::CloudFormation::Stack | CdkDemoStack
    3:50:21 PM | CREATE_IN_PROGRESS | AWS::EC2::InternetGateway | MainVpc/IGW
    3:50:21 PM | CREATE_IN_PROGRESS | AWS::EC2::VPC | MainVpc
    3:50:22 PM | CREATE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata/Default

    礙於小編公司內部資訊不能透露,這邊顯示示意圖~ 只要看到 ✅ 就對了

幾分鐘後,您應該會看到一個綠色的勾號,以及新建 CloudFormation 堆疊的 ARN (Amazon 資源名稱)。

測試一下是否成功了

如果有購買AWS Support就可以去Support建立一張CASE,屆時你就會在cloudwatch中看到log,而由於EventBridge的Target是lambda,因此會觸發你所設置的lambda,也會看到lambda中的log唷。

將資源刪除

這就不多廢話,試完了,如果不想要保留,當然要刪除它,直接 $ cdk destroy

Are you sure you want to delete: CdkEcsInfraStack (y/n)? y
CdkEcsInfraStack: destroying...

✅  CdkEcsInfraStack: destroyed

結語

CDK真的很方便,可以讓工程們使用自己擅長的語言進行Iac的開發,將CDK納入自己的技能樹,絕對有很大的幫助。

參考文獻

aws cdk
aws cdk doc
create-login-profile
create-user
add-user-to-group

how to build Static Website on gcs

前言

剛好有需求要將靜態網頁放到gcs上,因此就做個紀錄,因為只是紀錄,就不贅述一些自己知道的細節。

先安裝 gcloud

請參考 how to install gcloud


建立簡單的網站

1
2
3
$ npx create-react-app my-app
$ cd my-app
$ npm run build

建立Buckets

gsutil mb gs://[BUCKET_NAME]/


note: 如果要將網域設定為gcs的,那你就必須要命名為與網域相同的名稱,例如:www.example.com,那就要命名為www.example.com,gcs會告知你需要驗證domain


驗證domain

請到google search console去驗證你的domain,我這邊是使用TXT的方式驗證


label

建立服務帳戶

這邊要建立一個服務帳戶給予權限,並且拿到 gcloud-service-key.json

label

透過gcloud & gsutil 開始上傳

1
2
3
4
5
6
$ gcloud auth activate-service-account --key-file gcloud-service-key.json
$ gcloud config set project $GCP_PROJECT_ID
$ cd build
$ gsutil cp -r * gs://$GCS_BUCKET_NAME/
$ gsutil iam ch allUsers:objectViewer gs://$GCS_BUCKET_NAME
$ gsutil web set -m index.html gs://$GCS_BUCKET_NAME

附上gitlab ci/cd 腳本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
stages:
- build
- deploy

Build:
stage: build
image: node:14.20.0
before_script:
- yarn
script:
- yarn build:dev
cache:
untracked: true
paths:
- node_modules/
artifacts:
paths:
- ./build
only:
- develop

Deploy:
stage: deploy
image: google/cloud-sdk
script:
- echo "$GCP_SERVICE_KEY" > gcloud-service-key.json # Google Cloud service accounts
- gcloud auth activate-service-account --key-file gcloud-service-key.json
- gcloud config set project $GCP_PROJECT_ID
- cd build
- gsutil cp -r * gs://$GCS_BUCKET_NAME/
- gsutil iam ch allUsers:objectViewer gs://$GCS_BUCKET_NAME
- gsutil web set -m index.html gs://$GCS_BUCKET_NAME
dependencies:
- Build
only:
- develop

how to build x86_64 image on M1

透過M1會建立arm64版本, 透過 docker inspect IMAGE_ID可以看到

1
2
3
4
5
6
7
8
9
10
$ docker image inspect 0382b9b17bdb
{
...
"Architecture": "arm64",
"Variant": "v8",
"Os": "linux",
"Size": 223036168,
"VirtualSize": 223036168,
...
}

所以建立imagge時,改成

1
$ docker buildx build --platform=linux/amd64 . -t xxx

之後就可按照正常的docker tag、docker push進行操作

how to build nestjs on eks fargate

上次建立了nestjs專案,並且快速的建立了CRUD,這次我們來把nestjs專案放到eks上,不過不同的是,我們來使用fargate的方式建立eks~


fargate 是啥?AWS Fargate 是無伺服器,依用量計費的運算引擎,讓您專注於建置應用程式,而無需管理伺服器。AWS Fargate 適用於搭配 Amazon Elastic Container Service (ECS) 和 Amazon Elastic Kubernetes Service (EKS) 使用。

建立k8s集群,只是後面多了–fargate

1
$ eksctl create cluster --name YOUR_CLUSTER_NAME --version 1.21 --fargate

允許 AWS Identity and Access Management (IAM) 用於服務賬戶

1
$ eksctl utils associate-iam-oidc-provider --cluster YOUR_CLUSTER_NAME --approve

下載並安裝,允許 AWS 負載均衡器控制器代表您調用 AWS API 的 IAM 策略

1
$ curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.1/docs/install/iam_policy.js

1
2
3
$ aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json

建立一個iam service account

1
2
3
4
5
6
7
$ eksctl create iamserviceaccount \
--cluster=YOUR_CLUSTER_NAME \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::<AWS_ACCOUNT_ID>:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--approve

驗證一下是否建立成功

1
$ kubectl get serviceaccount aws-load-balancer-controller --namespace kube-system

Install the AWS Load Balancer Controller using Helm

1
$ helm repo add eks https://aws.github.io/eks-charts

安裝 TargetGroupBinding 自定義資源定義 (CRD)

1
$ kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"

開始安裝

1
2
3
4
5
6
7
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--set clusterName=YOUR_CLUSTER_NAME \
--set serviceAccount.create=false \
--set region=YOUR_REGION_CODE \
--set vpcId=<VPC_ID> \
--set serviceAccount.name=aws-load-balancer-controller \
-n kube-system

如果你要確認你的VPC ID
1
aws cloudformation describe-stacks --stack-name eksctl-andy-lab-cluster | jq -r '[.Stacks[0].Outputs[] | {key: .OutputKey, value: .OutputValue}] | from_entries' | jq -r '.VPC'

準備好docker file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FROM node:alpine AS dev

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build

FROM node:alpine as prod
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install --only=prod

COPY . .

COPY --from=dev /usr/src/app/dist ./dist

CMD ["node", "dist/main"]


nestjs專案

先建立 fargateprofile

1
eksctl create fargateprofile --cluster your-cluster --region your-region-code --name your-alb-sample-app --namespace default

建立Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: nestjs-k8s
spec:
selector:
matchLabels:
app: nestjs-k8s
replicas: 2
template:
metadata:
labels:
app: nestjs-k8s
spec:
containers:
- name: nestjs-k8s
image: xxxxxxxx.dkr.ecr.us-east-2.amazonaws.com/nestjs-demo:v1
ports:
- containerPort: 3000

建立service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: nestjs-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
selector:
app: nestjs-k8s
ports:
- protocol: TCP
targetPort: 3000
port: 3000
type: LoadBalancer

確認一下svc有沒有跑出LB位置

1
$ kubectl get svc

label

後記

其實使用fargate eks建立pod頗簡單,且不用特別去管理主機問題,省事許多,不過fargate有一些限制,這部分可以參考AWS的說明。

build CRUD with mysql on NestJs

安裝TypeORM

透過 Nest 官方支援的 TypeORM 來建立 DB。 首先安裝 TypeORM 相關的內容,並使用 typeorm init 來快速建立 typeorm 專案。


1
2
$ yarn add @nestjs/typeorm typeorm@0.2 mysql2
$ yarn typeorm init

此時你的目錄結構會變成這樣



接著配置Mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 打開 app.module.ts
// 將typeOrmModule填入
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '123456',
database: 'test',
autoLoadEntities: true,
synchronize: true,
}),
UserModule,
],
controllers: [],
providers: [],
})
export class AppModule {}

Note: 這邊注意,一般來說不會直接將資料庫帳密填入,因為是DEMO,我們直接在code中配置


我這邊透過docker-compose啟動mysql,如果需要,可參考 Andy’s Dev Tool

1
$ docker-compose -f docker-compose.yaml up -d mysql

開始進行簡單的CRUD

Nest CLI可以自動產生範本,跟許多知名的framework一樣,可以快速產生CRUD,我們就先透過這方式,並添加之前所述的API Doc描述吧。


1
2
3
# $ nest g resource [xxxx] 
# xxxx 我習慣使用table name, 這邊就用user當範例吧
$ nest g resource user

CLI會問你要產生怎樣的API,這邊先選REST吧,並且透過Nest CLI產生範本。

你會看到產生了許多檔案,我會將一些檔案刪除掉,因為有些是透過typeorm init產生的,但我們並不需要,刪除後結果如下~


1
2
3
4
5
6
7
# 你也可以手動刪除~
# rm -R entity/
# rm -R migration/
# rm ormconfig.json
# rm app.controller.ts
# rm app.controller.spec.ts
# rm app.service.ts


首先我們先編輯 user.entity.ts,為了示範,把基本的型態都建立一個


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
phone: string;

@Column()
age: number;

@Column()
createTime: Date;

@Column()
updateTime: Date;

@Column()
isDelete: boolean;
}

接著我們修改一下dto,這邊有兩個檔案,一個是新增一個是更新。
dto,因為api的request需要要進行驗證~所以我們先安裝一下所需套件


1
$ yarn add class-validator class-transformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// create-user.dto.ts
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { Column } from 'typeorm';

export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;

@IsString()
@IsNotEmpty()
phone: string;

@Column()
@IsNotEmpty()
age: number;

@Column()
createTime: Date;

@Column()
updateTime: Date;

@Column()
@IsOptional()
isDelete: boolean;
}

編輯module

1
2
3
4
5
6
7
8
9
10
11
12
13
// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
imports: [TypeOrmModule.forFeature([User])], // 將entity填入
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}

這時候我們重新啟動一下,就會看到db裡面幫你建立了user table,且裡面的欄位都建立好了,而針對User的CRUD的路由也都出現了唷~


1
$ yarn start:dev



接下來修改一下service拉,準備把Repository注入


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UserService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
) {}
create(createUserDto: CreateUserDto) {
createUserDto.createTime = createUserDto.updateTime = new Date();
createUserDto.isDelete = false;
return this.userRepository.save(createUserDto);
}

findAll() {
return this.userRepository.find();
}

findOne(id: number) {
return this.userRepository.findByIds([id]);
}

update(id: number, updateUserDto: UpdateUserDto) {
return this.userRepository.update(id, updateUserDto);
}

remove(id: number) {
return this.userRepository.delete(id);
}
}

接下來為了API DOC進行小微調,controller 的微調是為了定義DOC的描述和指定dto,這邊就進行POST的示範

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// user.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import {
ApiOperation,
ApiTags,
ApiQuery,
ApiBody,
ApiResponse,
} from '@nestjs/swagger';

@Controller('user')
@ApiTags('User')
export class UserController {
constructor(private readonly userService: UserService) {}

@Post()
@ApiOperation({ description: '新增一筆user使用' })
@ApiBody({ type: CreateUserDto, description: '新增user' })
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}

@Get()
@ApiOperation({ description: '取得user使用' })
@ApiBody({ type: CreateUserDto, description: '取得user' })
findAll() {
return this.userService.findAll();
}

@Get(':id')
@ApiOperation({ description: '獲取一個user' })
@ApiBody({ type: CreateUserDto, description: '獲取一個user' })
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}

@Patch(':id')
@ApiOperation({ description: '更新user' })
@ApiBody({ type: UpdateUserDto, description: '更新user' })
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}

@Delete(':id')
@ApiOperation({ description: '刪除user' })
@ApiBody({ type: CreateUserDto, description: '刪除user' })
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}

dto也要進行微調,dto的微調是為了進行參數驗證


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// create-user.dto.ts
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { Column } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ description: '姓名' })
name: string;

@IsString()
@IsNotEmpty()
@ApiProperty({ description: '手機' })
phone: string;

@Column()
@IsNotEmpty()
@ApiProperty({
description: '年齡',
type: Number,
minimum: 18,
default: 20,
})
age: number;

@Column()
createTime: Date;

@Column()
updateTime: Date;

@Column()
@IsOptional()
isDelete: boolean;
}

透過API DOC新增一筆資料~並確認~






結論

透過NestJs可以快速產生CRUD,以及API DOC,算是頗方便的~其實還有很多東西可以說,例如Auth和其他重要功能,這之後有空再說吧~


參考連結

how to using swagger on NestJs

前面有教大家安裝NestJs,今天要教大家快速建立API DOC,這樣除了方便查閱外,進行測試時也很方便容易。

先進行安裝swagger

1
$ npm install --save @nestjs/swagger swagger-ui-express

將main.ts改寫,增加SwaggerModule的setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Cats example') // 這邊是doc的大標題
.setDescription('The cats API description') // 這邊是doc的主要描述
.setVersion('1.0') // api version
.addTag('cats') // tag, 用來區分區塊用
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();

重新啟動,基本上沒有再多做其他配置,就會看到以下畫面以下畫面


熟悉一下NestJs CLI

基本上安裝了NestJs就會安裝了CLI

1
2
$ nest 
$ nest g resource [xxxx]


嘗試使用CLI將xxxx產生,你會發現很神奇的產生了許多預設檔案。
生成的代碼有 Controller、Service、Module,而且也有了 CRUD 的樣板。



下一章節在跟各位說明如何把CRUD填上~

how to install NestJs

what is nestjs

Nest (NestJS) 是一個用於構建高效、可擴展的Node.js服務器端應用程序的框架。它使用漸進式 JavaScript,使用 TypeScript 構建並完全支持TypeScript(但仍允許開發人員使用純 JavaScript 進行編碼),並結合了 OOP(面向對象編程)、FP(函數式編程)和 FRP(函數式反應式編程)的元素。


在底層,Nest 使用了強大的 HTTP 服務器框架,比如Express(默認),並且可以選擇配置為使用Fastify!


Nest 在這些常見的 Node.js 框架(Express/Fastify)之上提供了一個抽象級別,但也將它們的 API 直接暴露給開發人員。這使開發人員可以自由使用可用於底層平台的無數第三方模塊。


why nestjs

近年來,由於 Node.js,JavaScript 已成為前端和後端應用程序的網絡“通用語”。這催生了Angular、React和Vue等很棒的項目,它們提高了開發人員的工作效率,並支持創建快速、可測試和可擴展的前端應用程序。然而,雖然 Node(和服務器端 JavaScript)存在大量出色的庫、幫助程序和工具,但它們都沒有有效地解決架構的主要問題。

Nest 提供了一個開箱即用的應用程序架構,允許開發人員和團隊創建高度可測試、可擴展、鬆散耦合且易於維護的應用程序。該架構深受 Angular 的啟發。

how to install

1
2
3
$ npm i -g @nestjs/cli
$ nest new [project-name]
$ cd [project-name] && npm run start


打開瀏覽器並導航到 http://localhost:3000/


出現HelloWorld,就可以開始寫API拉~

how to install k8s on azure

  • 使用azure的doc範例
  • 基本上照著做應該不會有問題
  1. 先把azure的範例抓下來,並且進行build
    1
    2
    3
    $ git clone https://github.com/Azure-Samples/azure-voting-app-redis.git
    $ cd azure-voting-app-redis
    $ docker-compose up -d

使用瀏覽器測試一下,輸入http:localhost:8080

然後你也會看到三個image,這是後續需要用到的image,準備上傳到ACR

1
2
3
4
$ docker images
mcr.microsoft.com/azuredocs/azure-vote-front
mcr.microsoft.com/oss/bitnami/redis
tiangolo/uwsgi-nginx-flask

建立ACR

  1. 先建立group,下面指令會在 eastus 區域中建立名為 myResourceGroup 的資源群組

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ az group create --name andy-lab --location eastus
    {
    "id": "/subscriptions/cd4e3d37-eaae-493b-886f-436bc6012f4d/resourceGroups/andy-lab",
    "location": "eastasia",
    "managedBy": null,
    "name": "andy-lab",
    "properties": {
    "provisioningState": "Succeeded"
    },
    "tags": null,
    "type": "Microsoft.Resources/resourceGroups"
    }
  2. 使用 az acr create 命令建立 Azure Container Registry 執行個體,並提供您自己的登錄名稱。 登錄名稱在 Azure 內必須是唯一的,且包含 5-50 個英數字元。這邊取名叫 acrtest。

1
2
3
4
5
6
7
8
9
10
11
12
$ az acr create -n andylabar -g andy-lab -l eastasia --sku basic
{
"adminUserEnabled": false,
"anonymousPullEnabled": false,
"creationDate": "2022-05-24T03:01:43.942760+00:00",
"dataEndpointEnabled": false,
"dataEndpointHostNames": [],
"encryption": {
"keyVaultProperties": null,
"status": "disabled"
} ... 以下省略
}

你可以確認一下是否建立成功了

1
$ az acr list -o table




  1. 準備上傳你的image

要使用ACR前必須透過使用命令登入 ACR

1
2
$ az acr login -n andylabar
Login Succeeded

透過acr list找出acrLoginServer

1
2
3
4
$ az acr list --resource-group andy-lab --query "[].{acrLoginServer:loginServer}" --output table
AcrLoginServer
--------------------
andylabar.azurecr.io

上傳image

1
2
3
4
5
6
7
8
$ docker tag mcr.microsoft.com/azuredocs/azure-vote-front:v1 andylabar.azurecr.io/azure-vote-front:v1

$ docker push andylabar.azurecr.io/azure-vote-front:v1

$ az acr repository list --name andylabar --output table
Result
----------------
azure-vote-front

  1. 開始建立k8s集群到azure,需等待一段時間
1
2
3
4
5
6
az aks create \
--resource-group andy-lab \
--name andyAKSCluster \
--node-count 2 \
--generate-ssh-keys \
--attach-acr andylabar

  1. 登入集群
    1
    2
    $ az aks get-credentials --resource-group andy-lab --name andyAKSCluster
    Merged "andyAKSCluster" as current context in /Users/andywang/.kube/config

  1. 透過指令確認你本機的cluster是否建立
    1
    2
    3
    4
    5
    6
    7
    8
    $ kubectl config get-clusters
    NAME
    andyAKSCluster
    andy-lab.us-east-2.eksctl.io
    arn:aws:eks:us-east-2:185271018684:cluster/andy-lab
    docker-desktop
    test-cluster.us-east-2.eksctl.io
    arn:aws:eks:us-east-2:185271018684:cluster/test-cluster

Note: 如果你要切換cluster,請執行 kubectl config set current-context [cluster name]


  1. 將image部署到AKS上了
    1
    2
    3
    4
    5
    6
    7
    8
    containers:
    - name: azure-vote-front
    image: mcr.microsoft.com/azuredocs/azure-vote-front:v1

    # 改成你自己的acr
    containers:
    - name: azure-vote-front
    image: andylabar.azurecr.io/azure-vote-front:v1

1
2
3
4
5
$ kubectl apply -f azure-vote-all-in-one-redis.yaml
deployment.apps/azure-vote-back created
service/azure-vote-back created
deployment.apps/azure-vote-front created
service/azure-vote-front created

  1. 測試自己的應用程式
    1
    $ kubectl get service azure-vote-front


參考文獻

azure k8s