Day 15 – 使用 CDK 控制 Elastic Load Balancing – Network Load Balancer

在負載平衡中 AWS 提供了我們三種負載平衡 Application Load Balancer(ALB)、Network Load Balancer(NLB) 與 Classic Load Balancer,而 Classic Load Balancer 屬於上一代的負載平衡所以並不會提到它

https://ithelp.ithome.com.tw/upload/images/20201001/20117701qD5IUlduzr.jpg

而 Network Load Balancer 與 Application Load Balancer 最大的不同點在於 Network Load Balancer 為第四層(Transport Layer) 服務而 Application Load Balancer 處於 第七層(Application Layer) 服務,如果以我們目前介紹的用法來出其實沒有太大的差別,因為我們都是把它當 HTTP 的負載平衡器來使用,不過還是先體驗一下吧!

建立 Load Balancing

建立 Network Load Balancer(NLB)

竟然說到 NLB 當然是先建立它拉!

const lb = new elbv2.NetworkLoadBalancer(this, "LB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "NetworkLoadBalancerDNS", {
  value: lb.loadBalancerDnsName,
});

新增 Listener

建立一個 port 80 的 Listener

const listener = lb.addListener("Listener", {
  port: 80,
});

新增 Target 到 Listener

這邊分為兩個建立方法,大家可以評估自己的需求來選用想要用的方法

使用 Instance type 方法建立

Target 設定為 Instance 與 IP 最大的不同在於 Security Group 的設定

使用 Instance Type 可以想像所有的流量會從 ALB 外面直接流進機器,而 NLB 不像是 ALB 一樣擁有 Security Group 因此無法使用 Security Group 對接的方法來保護機器,所以說對應到機器 Security Group 的 Port 需要全開,不然會沒有辦法通

listener.addTargets("Targets", {
  port: httpPort,
  targets: [
    new targets.InstanceTarget(ec2Instance),
  ],
});

使用 IP type 方法建立

Target 設定為 IP Type 的好處在於所有的流量會經由內網丟進去,所以在機器 Security Group 的地方就可以選用 VPC CIDR 的方法指定,以安全性來說可以阻止使用者使用機器的 Public IP 連線或許有的人比較喜歡此方法。 有人可能會詢問那這樣內部的機器是不是就沒有辦法知道 client 的 IP 呢?如果要支援大家可以開啟 proxy protocol version 2 的支援就可以了,不過此 Protocol 需要軟體支援大家需要特別注意!

listener.addTargets("Targets", {
  port: httpPort,
  targets: [
    new targets.IpTarget(ec2Instance.instancePrivateIp),
  ],
});

詳細的 Security 規則大家可以參考 AWS 文件

整理整個服務的 Code

只要使用以上兩個步驟就可以把整個 Network Load Balancer 完成,感覺是不是很快呢 XD 因為這邊的步驟怕大家搞混,所以我們把 Instance type 建立方法與 IP type 建立方法獨立成兩種方法讓大家比較好理解

使用 Instance type 方法建立

const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);

mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  "allow public http access"
);

const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
  }),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});
new cdk.CfnOutput(this, "EC2PublicDns", {
  value: ec2Instance.instancePublicDnsName,
});
new cdk.CfnOutput(this, "EC2PublicIp", {
  value: ec2Instance.instancePublicIp,
});

const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});
asset.grantRead(ec2Instance.role);

const httpPort = 80;
const lb = new elbv2.NetworkLoadBalancer(this, "LB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "NetworkLoadBalancerDNS", {
  value: lb.loadBalancerDnsName,
});

const listener = lb.addListener("Listener", {
  port: httpPort,
});

listener.addTargets("Targets", {
  port: httpPort,
  targets: [
    new targets.InstanceTarget(ec2Instance),
  ],
});

使用 IP type 方法建立

const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);

mySecurityGroup.addIngressRule(
  ec2.Peer.ipv4(vpc.vpcCidrBlock),
  ec2.Port.tcp(80),
  "allow vpc cidr http access"
);

const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
  }),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});
new cdk.CfnOutput(this, "EC2PublicDns", {
  value: ec2Instance.instancePublicDnsName,
});
new cdk.CfnOutput(this, "EC2PublicIp", {
  value: ec2Instance.instancePublicIp,
});

const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});
asset.grantRead(ec2Instance.role);

const httpPort = 80;
const lb = new elbv2.NetworkLoadBalancer(this, "LB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "NetworkLoadBalancerDNS", {
  value: lb.loadBalancerDnsName,
});

const listener = lb.addListener("Listener", {
  port: httpPort,
});

listener.addTargets("Targets", {
  port: httpPort,
  targets: [
    new targets.IpTarget(ec2Instance.instancePrivateIp),
  ],
});

測試 NLB 網址

目前拿到的

  • CdkEc2Stack.LoadBalancerDNS: http://CdkEc-LB8A1-11UM2UIFJMS5H-446ba4836b259a16.elb.us-west-2.amazonaws.com/

測試 Test Page

打開它試試看 https://ithelp.ithome.com.tw/upload/images/20200929/201177010EWKEhmVm0.png

測試 phpinfo.php

http://cdkec-albae-1qhysttihzwc-231561288.us-west-2.elb.amazonaws.com/phpinfo.php

https://ithelp.ithome.com.tw/upload/images/20200929/20117701nwoy0p1IgZ.png

結論

基本上與昨天的結果會是一樣的,因為測試的範例是使用 HTTP 網頁作為範例,所以比較沒有感覺

而 Network Load Balancer 主要是用來解決需要處理第四層以上的服務所生的,想要理解更多可以查看一下 AWS Console 打開 Listeners 可以看到除了 TCP 還可以支援 TLS、UDP 或是同時處理 TCP 與 UDP 的服務 https://ithelp.ithome.com.tw/upload/images/20200929/20117701sBEAt55WxC.png

TLS 支援

以 TLS 來說我們可能有一個服務是 RTSP 而我們的程式可能因為某些原因不方便直接讓程式支援 TLS 可以又必須支援 RTSP over SSL 的時候所使用

UDP 支援

以 UDP 來說可能是 DNS 服務或是 QUIC

TCP 與 UDP

可能類似 DNS


以上為今日的 Network Load Balancer 介紹,希望有幫助到大家

Day 14 – 使用 CDK 控制 Elastic Load Balancing – Application Load Balancer

說到部署機器怎麼可以少了 Load Balancing (LB) 呢?今天就來介紹 LB 吧!

https://ithelp.ithome.com.tw/upload/images/20200930/20117701GtFoof8DD0.jpg

在 CDK 的文件上面都是建議使用 Auto Scaling Group (ASG) 來設定,因為這樣才是一個高可用性架構,不過我覺得可以從單獨一個服務開始說起,畢竟單獨解釋一個服務會比較好理解,況且我知道很多公司的服務其實一台機器就可以頂住了 XD

建立 Load Balancing

建立 ALB

這次新增了一個新的 package 是 @aws-cdk/aws-elasticloadbalancingv2 它用來處理 Elastic Load Balancing internetFacing 預設是 false,意思是創建一個內網的 Load Balancing 而我們這邊目前需要的是外網的所以要把它設定成 true 使用 CfnOutput 把 Load Balancing 的 DNS 印出來

import * as elbv2 from "@aws-cdk/aws-elasticloadbalancingv2";

const alb = new elbv2.ApplicationLoadBalancer(this, "ALB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "LoadBalancerDNS", {
  value: alb.loadBalancerDnsName,
});

新增 Listener

目前只有一個 Listener 使用 port 80,因為目前的服務是要給 HTTP 的

const listener = alb.addListener("Listener", {
  port: 80,
  open: true,
});

新增 Target 到 Listener

以昨天的服務來說我們有一個 /phpinfo.php 目前先用它來做 healthCheck 頁面之後會再修正它,用這個頁面來做其實不太正確不過就先頂著用吧 XD 另外加入 ec2Instance 當我們的 Target,如此設定就可以讓我們的 ALB 後面有一個 Target Group 而裡面有一台 ec2Instance 了 這邊需要注意一下我們的 targets 要使用 aws-elasticloadbalancingv2-targets package 來作為我們的 targets,因為 elbv2.InstanceTarget 要棄用了,所以不要使用 new elbv2.InstanceTarget(ec2Instance.instanceId) 呦!

import * as targets from "@aws-cdk/aws-elasticloadbalancingv2-targets";

listener.addTargets("Targets", {
  healthCheck: {
    enabled: true,
    path: "/phpinfo.php",
  },
  port: 80,
  targets: [new targets.InstanceTarget(ec2Instance)],
});

新增 Security Group

這邊需要注意一下預設的 ALB 會把 outbound 完全丟掉,所以需要特別新增 listener 到 EC2 的 Security Group

listener.connections.allowTo(ec2Instance, ec2.Port.tcp(httpPort));

ALB Security Group 如何限制 outbound

在閱讀 CDK Source Code 的時候發現一個很有趣的東西如果要阻止所有流量的時候會建立一條規則

  • IP: 255.255.255.255/32 – 實際上沒有任何機器可以擁有此 IP
  • Protocol: ICMP
  • Port 252 – 實際上沒有定義的 Type

可以查看 Wiki 網際網路控制訊息協定

const MATCH_NO_TRAFFIC = {
  cidrIp: '255.255.255.255/32',
  description: 'Disallow all traffic',
  ipProtocol: 'icmp',
  fromPort: 252,
  toPort: 86,
};

https://github.com/aws/aws-cdk/blob/v1.64.1/packages/@aws-cdk/aws-ec2/lib/security-group.ts##L508-L523

整理一遍整個服務的 Code

最後我們一樣把整個 Source Code 整理一次給大家,讓大家方便好部署 XD

const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

// 設定 Security Group
const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);

// 設定上傳的 User Data 腳本
const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});

// 設定 EC2 機器
const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
  }),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "Clarence_KeyPair",
});
new cdk.CfnOutput(this, "EC2PublicDns", {
  value: ec2Instance.instancePublicDnsName,
});
new cdk.CfnOutput(this, "EC2PublicIp", {
  value: ec2Instance.instancePublicIp,
});

const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});
asset.grantRead(ec2Instance.role);

// 設定 Load Balancer
const httpPort = 80;
const alb = new elbv2.ApplicationLoadBalancer(this, "ALB", {
  vpc,
  internetFacing: true,
});
new cdk.CfnOutput(this, "LoadBalancerDNS", {
  value: alb.loadBalancerDnsName,
});

const listener = alb.addListener("Listener", {
  port: httpPort,
  open: true,
});
listener.connections.allowTo(ec2Instance, ec2.Port.tcp(httpPort));

listener.addTargets("Targets", {
  healthCheck: {
    enabled: true,
    path: "/phpinfo.php",
  },
  port: httpPort,
  targets: [new targets.InstanceTarget(ec2Instance)],
});

測試 ALB 網址

我目前拿到的

  • CdkEc2Stack.LoadBalancerDNS: http://cdkec-albae-1qhysttihzwc-231561288.us-west-2.elb.amazonaws.com/

測試 Test Page

打開它試試看 https://ithelp.ithome.com.tw/upload/images/20200927/20117701n9hbrNUmIH.png

測試 phpinfo.php

http://cdkec-albae-1qhysttihzwc-231561288.us-west-2.elb.amazonaws.com/phpinfo.php

https://ithelp.ithome.com.tw/upload/images/20200927/20117701Jy0wVjDCNf.png

結論

以上是今天的 ALB 介紹希望有幫助到大家,話說我有在網路上找了找好像目前沒有人寫過單一台 Instance 接在 ALB 後面的文,今天就介紹給大家啦!

想要看更多嗎?歡迎到我的部落格參觀

文章內容主要是網路或是程式開發類型的文章

Day 13 – 使用 CDK 部署 EC2 讓 User Data 與 S3 結合我的機器也要自動部署!

今天來說明我們想要一台擁有 LAMP 的機器要怎麼讓 CDK 幫我們自動部署起來吧!

https://ithelp.ithome.com.tw/upload/images/20200929/2011770199UlrctO6e.jpg

建立 HTTP Server

寫安裝腳本檔

要建立 HTTP Server 首先需要先準備一個安裝腳本,而這個腳本需要請 User Data 幫我們跑執行指令 先建立一個資料夾 ec2-config 在裡面新增一個檔案 configure.sh,先對腳本做一個說明

  • 第三行:更新 yum
  • 第四行:安裝 LAMP
  • 第五行:安裝 Apache 與 maria DB
  • 第六行:啟動 Apache
  • 第七行:設定開機啟動 Apache
  • 第八行:把 ec2-user 加入 apache 群組
  • 第九行:設定 /var/www 目錄擁有者
  • 第十行:設定 /var/www 目錄權限
  • 第十一行:設定所有資料夾目錄權限
  • 第十二行:設定所有檔案權限
  • 第十三行:設定 phpinfo 檔案

以功能來看此腳本做了

  • 更新機器
  • 安裝必要程式
  • 設定檔案目錄權限
  • 新增一個 phpinfo.php 網頁
#!/bin/bash
uname -a
yum update -y
amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
yum install -y httpd mariadb-server
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php

上傳腳本到 S3

這次使用一個新的 CDK package 來完成這件事情,以下的功能就是上傳剛剛建立的 ec2-config/configure.sh 安裝腳本,並顯示上傳結果。

import * as assets from "@aws-cdk/aws-s3-assets";

const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});
new cdk.CfnOutput(this, "S3BucketName", { value: asset.s3BucketName });
new cdk.CfnOutput(this, "S3ObjectKey", { value: asset.s3ObjectKey });
new cdk.CfnOutput(this, "S3HttpURL", { value: asset.httpUrl });
new cdk.CfnOutput(this, "S3ObjectURL", { value: asset.s3ObjectUrl });

EC2 執行 User Data

在處理 User Data 我們需要處理兩個部分

  1. 從 S3 下載腳本
  2. 使用 User Data 執行腳本

從 S3 下載腳本

在 CDK 裡面有提供 User Data 可以直接下載 S3 檔案的 function,所以我們只需要填入 bucket 在上一步的 asset 有使用的 asset.bucket 與 bucketKey 一樣在上一部 asset 有使用過的 asset.s3ObjectKey,最後不要忘記給 Instance role 不然檔案會無法下載

const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
asset.grantRead(ec2Instance.role);

使用 User Data 執行腳本

讓 EC2 User Data 直接執行上傳檔案的 localPath

ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});

整體部署腳本整合

部署 Web APP

在這邊我們使用單獨一台機器來做示範,所以並還沒有加入昨天說的 Bastion Host 而這次部署的內容含有:

  • VPC
  • SecurityGroup
    • 開啟 22 Port
    • 開啟 80 Port
  • 上傳 ec2-config/configure.sh 提供給 User Data 使用
  • 架設 EC2
    • 等級 t3.nano
    • 使用 Image: AMAZON LINUX 2
    • Subnet: Public
    • User Data: 執行 configure.sh
const vpc = new ec2.Vpc(this, "VPC", {
  maxAzs: 3,
  natGateways: 0,
});

const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  "allow public http access"
);

const asset = new assets.Asset(this, "Asset", {
  path: path.join(__dirname, "../", "ec2-config", "configure.sh"),
});

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
  }),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});
new cdk.CfnOutput(this, "EC2PublicDns", {
  value: ec2Instance.instancePublicDnsName,
});
new cdk.CfnOutput(this, "EC2PublicIp", {
  value: ec2Instance.instancePublicIp,
});

const localPath = ec2Instance.userData.addS3DownloadCommand({
  bucket: asset.bucket,
  bucketKey: asset.s3ObjectKey,
});
ec2Instance.userData.addExecuteFileCommand({
  filePath: localPath,
  arguments: "--verbose -y",
});
asset.grantRead(ec2Instance.role);

測試網頁

測試 Apache

在剛剛 CDK 的 output 找到 EC2PublicDns 看到測試 Web 頁面

CdkEc2Stack.EC2PublicDns = ec2-54-213-111-80.us-west-2.compute.amazonaws.com

https://ithelp.ithome.com.tw/upload/images/20200926/201177016mwfvWj5H1.png

測試 PHP

輸入測試 phpinfo 的網址測試 PHP 是否正常執行

http://ec2-54-213-111-80.us-west-2.compute.amazonaws.com/phpinfo.php

https://ithelp.ithome.com.tw/upload/images/20200926/20117701hvXeRTmeWO.png

https://ithelp.ithome.com.tw/upload/images/20200926/20117701QALHGEVkVJ.png 如果打開網頁直接出現了文字代表 PHP 安裝部分有問題請修改 AMI 為 AMAZON LINUX 2,這點要注意一下

結論

如此就擁有一台自動部署 lamp (Linux + Apache + MariaDB + PHP) 的機器了 終於說到 DevOps 的重點拉!(つ> _◕)つ︻╦̵̵͇̿̿̿̿╤───

Day 12 – 使用 CDK 部署 Bastion Host 防禦主機!

要說明自動部署前我們應該要先說明怎麼好好保護我們的內網機器而說到內網機器就會說到我們的 Bastion Host,畢竟大家都不會想要我們的機器在外面裸奔吧 XD 所以在介紹自動部署之前我們先來介紹 Bastion Host 中文可能稱它為防禦主機或是堡壘主機

https://ithelp.ithome.com.tw/upload/images/20200928/201177014SQspjkASO.jpg

Bastion Host

一般來說比較偷懶的時候會在 Application 的 EC2 上面設定一個 Static IP 白名單 22 Port 來保護我們的機器而這其實不太安全,所以比較好的做法其實是設定一台 Bastion Host 來當我們的跳板機,所有的連線會經由這台跳板機連到內部的 private 網路主機,這其實是相對安全的一個方法,以架構來看我們會希望它是這個架構。

https://ithelp.ithome.com.tw/upload/images/20200928/20117701HMzU5AKzFO.png (圖片來源:AWS Security Blog

而我們今天就要來教大家如何使用 CDK 架設一台 Bastion Host

使用 SSH 直接連接 Bastion Host

不知道大家有沒有發現前幾天的教學文其實是在教大家如何建立自己的 Bastion Host,而 CDK 其實有一個簡單的 function 可以簡單的建立 Bastion Host

在這邊先把 SSH 開成 any 有需要設定 static ip 可以使用host.allowSshAccessFrom(ec2.Peer.ipv4('192.192.192.192/32'));

這樣就不用寫昨天的一堆程式只要一行就可以達成拉!

const host = new ec2.BastionHostLinux(this, "BastionHost", {
  vpc,
  subnetSelection: { subnetType: ec2.SubnetType.PUBLIC },
});
host.allowSshAccessFrom(ec2.Peer.anyIpv4());

使用 AWS Systems Manager 的 Session Manager 連接 Bastion Host

另外如果使用 Session Manager 連入 Bastion 其實可以更簡單的部署 Bastion,看起來是不是又更短了呢!

const host = new ec2.BastionHostLinux(this, "BastionHost", {
  vpc,
});

Session Manager 使用本機 SSH 連線

相信大家平常習慣連接 EC2 Instance 機器方法是 SSH,而 Session Manager 也支援直接使用 SSH 的方法,在這裡我們可以使用工具 aws-ssm-ec2-proxy-command 來簡單達成此功能

安裝 SSH Proxy Command

  • 先下載 aws-ssm-ec2-proxy-command.sh 把它放入 ~/.ssh/aws-ssm-ec2-proxy-command.sh
  • 加入執行權限 chmod +x ~/.ssh/aws-ssm-ec2-proxy-command.sh

設定 SSH Config

編輯 ~/.ssh/config 加入

host i-* mi-*
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand ~/.ssh/aws-ssm-ec2-proxy-command.sh %h %r %p ~/.ssh/id_rsa.pub
  StrictHostKeyChecking no

如此我們就可以使用 ssh <INSTACEC_USER>@<INSTANCE_ID> 連接主機拉! 假設我們的機器 ID 是 i-0de45ffd579418348 就可以使用

$ ssh -A ec2-user@i-0de45ffd579418348

分析一下 aws-ssm-ec2-proxy-command 這隻腳本

其實它與我們昨天教大家登入 SSM 指令最大的不同在於

aws ec2-instance-connect send-ssh-public-key  \
  --instance-id "$ec2_instance_id" \
  --instance-os-user "$ssh_user" \
  --ssh-public-key "file://$ssh_public_key_path" \
  --availability-zone "$instance_availability_zone"

此指令會把我們設定的 ~/.ssh/id_rsa.pub 上傳上去主機裡面,所以當我們下次使用 SSH 指令的時候就可以直接登入主機啦!

連接到 private network 的 App Host

預設設定 App Host 會放入本機的 SSH Key 並且只有 Bastion Host 可以連入,這樣我們就可以先跳到 Bastion Host 再連到 App Host 拉! https://ithelp.ithome.com.tw/upload/images/20200926/20117701t6d1mfFudM.png

參考資料

  • https://globaldatanet.com/blog/ssh-and-scp-with-aws-ssm
  • https://github.com/qoomon/aws-ssm-ec2-proxy-command
  • https://aws.amazon.com/tw/blogs/aws/new-port-forwarding-using-aws-system-manager-sessions-manager/
  • https://aws.amazon.com/tw/blogs/security/how-to-record-ssh-sessions-established-through-a-bastion-host/

Day 11 – 使用 CDK 部署 EC2 我需要為機器加入 Role Policy 與執行 User Data

使用 EC2 一定要介紹到 Role Policy 與 User Data,在 AWS 因為服務很多相關的服務如果都使用 IAM Key 去處理會比較麻煩而且久了沒有換可能會有資安風險,這時候就可以使用 Role Policy 來幫我們做到這件事情,而機器啟動後通常會想要跑一些自己的腳本來設定一下機器,要達成這件事情就需要用到 User Data 拉!因此今天就來介紹他們。

https://ithelp.ithome.com.tw/upload/images/20200927/20117701o94CjDzA4q.jpg

複習 EC2 部署程式

昨天還沒帶大家看部署的結果,今天先來部署程式並帶大家看一下昨天部署的結果後面會用這段程式繼續延伸

const vpc = new ec2.Vpc(this, "VPC", {
  natGateways: 0,
});

const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  description: "Allow ssh access to ec2 instances from anywhere",
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: new ec2.AmazonLinuxImage(),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});
  • 檢查一下 AWS console,會看到 Instance 部署在 Public subnet https://ithelp.ithome.com.tw/upload/images/20200924/20117701XOOoO0CrJo.png

  • 再來看一下 CDK 部署的 SG

    • Inbound 0.0.0.0 開啟 22 port 的 TCP
    • Outbound 0.0.0.0 全部都開啟 https://ithelp.ithome.com.tw/upload/images/20200924/20117701NR2LiOJXyZ.png

使用 SSH 測試登入

到 command line 使用 SSH 登入,因為我們部署的機器是 Amazon Linux 它預設的 user 名稱是 ec2-user,如果使用的是 Ubuntu 預設的登入帳號就是 ubuntu 了

不同版本的機器可能登入的 user 預設帳號不同,大家需要注意一下

https://ithelp.ithome.com.tw/upload/images/20200924/20117701prpcJtS60H.png

部署支援 SSM 的 EC2

應該不少朋友之前有用過 SSM 登入機器,它可以讓使用者不用先放入 Public Key 由 AWS 代勞在需要使用的時候放入臨時的 Public Key 來登入,如此不僅不用保管 SSH key 也可以減少資安風險 現在來教大家如何使用 CDK 部署一台可以支援 SSM 的 instance,要部署我們需要先準備

  1. Role Policy
  2. 安裝 SSM agent

Role Policy

以需求來看我們啟動 SSM 需要以下 action

  • ssmmessages:*
  • ssm:UpdateInstanceInformation
  • ec2messages:*

通常最麻煩的地方是要收集 action 收集好就把它用 array 的方法填入就 OK 了! 其實與以前準備好 action 填到 AWS Console 差不多,現在有了 CDK 就連 AWS Console 都不用進去拉!是不是超方便的 ~

ec2Instance.addToRolePolicy(
  new iam.PolicyStatement({
    actions: [
      "ssmmessages:*",
      "ssm:UpdateInstanceInformation",
      "ec2messages:*",
    ],
    resources: ["*"],
  })
);

User data

準備安裝腳本

以這次的目標需要安裝 SSM agent 我們把指令準備好,安裝的方法可以參考 AWS 文件 How do I install AWS Systems Manager Agent (SSM Agent) on an Amazon EC2 Linux instance at launch?

yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm

使用 CDK 執行安裝腳本

使用 addUserData 填入執行的字串就 OK 了!如果有多個指令也可以使用 && 把它串起來執行

ec2Instance.addUserData(
  "yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm"
);

結合昨天的程式

Role Policy 與 User Data 都準備好就把它合併到昨天的程式裡面

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: new ec2.AmazonLinuxImage(),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});

ec2Instance.addToRolePolicy(
  new iam.PolicyStatement({
    actions: [
      "ssmmessages:*",
      "ssm:UpdateInstanceInformation",
      "ec2messages:*",
    ],
    resources: ["*"],
  })
);

ec2Instance.addUserData(
  "yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm"
);

使用 AWS Console 測試 SSM 登入

  1. 右上角的 Connect 點選

https://ithelp.ithome.com.tw/upload/images/20200925/20117701EYWS1MUcWd.png

  1. 選擇 Session Manager 然後點選 Connect https://ithelp.ithome.com.tw/upload/images/20200925/20117701fylZZNJbrh.png

  2. 如此就可以在網頁執行 SSH 功能拉! https://ithelp.ithome.com.tw/upload/images/20200925/20117701NkUXexi67y.png

使用 Command 測試 SSM 登入

還記得前面說的吧?我們不僅可以使用 AWS Console 登入 SSM 其實也可以使用自己的 Command Line 登入 AWS 機器,首先需要先準備 SSM Tool 並且準備 Instance ID 以我這次部署的 Instance 來說我的 Instance ID 是 i-06af4a77c0be9b4ad 就輸入

$ aws ssm start-session --target i-06af4a77c0be9b4ad

可以看到我使用 SSM agent 登入機器拉!雖然我登入完就輸入 exit 離開了 XD https://ithelp.ithome.com.tw/upload/images/20200925/20117701XPEZQcU1f4.png

以上是今日的 Role Policy 與 User Data,因為準備花了比較長的時間所以內容比較少請大家見諒

Day 10 – 使用 CDK 部署 EC2

今天終於要來講部署 EC2 的方法拉!不過在講 EC2 之前要先介紹 VPC 畢竟沒有 VPC 就沒有地方可以放機器拉 ~

https://ithelp.ithome.com.tw/upload/images/20200926/20117701uFwSRxkx2E.jpg

CDK 建立 VPC

如果要在 CDK 新增一個簡單的 VPC 非常簡單只要簡單的一行即可,創建完的預設

  • CIDR:10.0.0.0/16
  • AZ:預設為 3
  • NAT gateway:預設為每個 AZ 各一個
const vpc = new ec2.Vpc(this, 'VPC');

Subnet 分為三種

  • Public (開放 subnet): Public subnet 直接連接 Internet 希望直接可以取得 public IP 請將機器放在這個 subnet
  • Private (私人 subnet): 無法直接使用 Internet 預設會在每個 subnet 創建一個 NAT gateway

    NAT gateway 是需要付費的這點需要請大家注意一下

  • Isolated (隔離 subnet): subnet 不會連接 Internet 也不會連接 NAT gateway 一個隔離的 subnet,它只能連到 VPC 裡面的機器或是被 VPC 裡面的其他機器連

https://ithelp.ithome.com.tw/upload/images/20200924/20117701sSpUEmrb65.png

https://ithelp.ithome.com.tw/upload/images/20200924/20117701Cw5F5rGkiC.png

NAT gateway

如果沒有 NAT gateway 需求可以把 natGateways 設定為 0,不過這樣就不會創建 Private subnet

const vpc = new ec2.Vpc(this, "VPC", {
  natGateways: 0,
});

https://ithelp.ithome.com.tw/upload/images/20200924/20117701qXi9oSWz67.png

可以檢查一下 NAT Gateway 並不會看到有服務

如果想要有 Private subnet 又不想要 NAT gateway 這麼多可以把 natGateways 設定為 1 他們就會自動共用 NAT gateway

const vpc = new ec2.Vpc(this, "VPC", {
  natGateways: 1,
});

https://ithelp.ithome.com.tw/upload/images/20200924/201177019agU6wIgRu.png

可以檢查一下 NAT Gateway 會看到有服務

CDK 建立 EC2

創建簡單的 EC2 只要以下程式就可以創建,不過目前這台機器沒有開放 SG,而且放在 Isolated Subnet 或是 Private Subnet 因此一般來說是摸不到它的

const vpc = new ec2.Vpc(this, 'VPC');

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: new ec2.AmazonLinuxImage(),
});

https://ithelp.ithome.com.tw/upload/images/20200924/20117701yDWLyEx1WS.png

但是如果需求是 IAM image 開起來就可以用的需求就只要修改一下 machineImage 就可以了

設定一台外網可以連入的 EC2

要達成外網可以連入的需求我們需要完成幾件事情

  • SG 設定 22 port 可以連入
  • EC2 要在 Public Subnet
  • EC2 需要含有 Public Key

通常我們應該會把 SSH port 設定成只有某個 Static 可以連入,不過因為範例的關係就先全開。範例的 keyName 為 KeyPair,大家平常應該會有自己習慣的 key pair name 記得修改他!

const vpc = new ec2.Vpc(this, "VPC", {
  natGateways: 0,
});

const mySecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
  vpc,
  description: "Allow ssh access to ec2 instances from anywhere",
  allowAllOutbound: true,
});
mySecurityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  "allow public ssh access"
);

const ec2Instance = new ec2.Instance(this, "Instance", {
  vpc,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T3,
    ec2.InstanceSize.NANO
  ),
  machineImage: new ec2.AmazonLinuxImage(),
  securityGroup: mySecurityGroup,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PUBLIC,
  },
  keyName: "KeyPair",
});

以上為今天介紹的基本 EC2 機器介紹,如此就可以自行創建簡單的 EC2 了

Day 9 – 部署靜態網頁 (番外篇)

在昨天的靜態網頁教學中還沒說明限制的部分,今天準備了幾個問題來回答

https://ithelp.ithome.com.tw/upload/images/20200925/20117701j0DMhuAFMC.jpg

問題列表

  • 在昨天的教學文件裡面有提到如果要使用 acm.Certificate 需要切換 Region 到 us-east-1 這是為什麼呢?
  • 我們竟然都使用 CDK 了,那可不可以讓 ACM 在 us-east-1 而其他服務在不同區呢?

在昨天的教學文件裡面有提到如果要使用 acm.Certificate 需要切換 Region 到 us-east-1 這是為什麼呢?

這是因為 CloudFront 使用 ACM 有限制條件,我們可以在 AWS 文件裡面找到 如果要使用 CloudFront 必須使用 US East (N. Virginia) 區域的憑證

我們竟然都使用 CDK 了,那可不可以讓 ACM 在 us-east-1 而其他服務在不同區呢?

類似的問題其實在 2020/07/28 有被提出過 [aws-certificatemanager] Create certificate in us-east-1 and use it in a different region #9274

簡單說明一下這則 issue 的內容

這位作者想要創建 AWS Cognito 的服務而他的 CDK stack 在 eu-central-1,如果要使用自定義網域憑證需要在 us-east-1 要如何分享憑證到 AWS Cognito.

AWS 人員給予的回應簡述

  1. 使用兩個不同的 CDK stack 來處理
  2. 使用 acm.CertificateValidation 來處理

方法一:以目前來說不可行,目前在 Certificates ARN 還沒有解法,可以在此 commit 看到詳細說明 方法二:限制只有使用 Route53 的使用者可以使用此解法

談討為什麼 acm.Certificate 不可以指定 Region 而 acm.CertificateValidation 可以指定

首先先看 CDK 文件可以看到 Certificate 的 class 裡面沒有可以填入 region 的地方,而 DnsValidatedCertificate 有一個 Construct Props region

分析 Certificate 實作

我們到 Github 看一下 Certificate 的實作方法 首先看到 import 的地方只有 route53, core 與 certificatemanager.generated

特別說明一下 certificatemanager.generated 代表 AWS CloudFormation-only 或稱 L1 也就是它是原生的 CloudFormation 可參考文件

因此可以知道 Certificate 只有使用原生的 CloudFormation

import * as route53 from '@aws-cdk/aws-route53';
import { Construct, IResource, Resource, Token } from '@aws-cdk/core';
import { CfnCertificate } from './certificatemanager.generated';
import { apexDomain } from './util';

分析 DnsValidatedCertificate 實作

先看到 Github DnsValidatedCertificate 的實作方法,一樣我們可以從 import 先做概略分析它有一個 lambda 代表這邊的功能有人會用它實作

import * as path from 'path';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as route53 from '@aws-cdk/aws-route53';
import * as cdk from '@aws-cdk/core';
import { CertificateProps, ICertificate } from './certificate';

再來我們往下看到 L76-L94 就可以看到這邊有一個 Lambda 的呼叫檔案位置在

path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')

也就是 dns_validated_certificate_handler/lib/index.js 而輸入 region 的位置在 L241 因此使用 DnsValidatedCertificate 才可以輸入 region 位置,不然依造目前 CloudFormation 的特性是沒辦法跨 region 的

今日主要帶大家簡單的看一下 CDK Source code 有個體驗,希望今天的分享有幫到大家

Day 8 – 部署靜態網頁

前面說了這麼多的 API 部署,差不多也該來個網頁了,今天就來教大家如何部署一個靜態網頁吧!

https://ithelp.ithome.com.tw/upload/images/20200924/20117701Rqhfw3U79A.jpg

創建靜態網頁專案

創建專案

$ mkdir cdk-static-site && cd cdk-static-site
$ cdk init --language typescript

準備我們需要的 package

要創建一個靜態網頁我們需要幾個服務,我們先把它列出來

  • s3: 存放靜態網頁的地方
  • ACM: 網頁需要 HTTPS 憑證
  • CloudFront: 使用者開啟網頁第會先到 CDN 再讓 CDN 去 S3 取得靜態網頁檔 都想好了就先把我們需要的 package 裝起來吧!每次要裝的 npm package 其實都滿多的,這邊來教大家一個快速裝 npm package 的方法 我們因為的專案都是來自於 @aws-cdk 只有後面的專案名稱不一樣就可以用快速指令把它串接起來,如此就可以一行指令快速的把 package 裝起來了
$ npm install @aws-cdk/{aws-s3,aws-s3-deployment,aws-certificatemanager,aws-cloudfront}

準備 S3 Bucket

用過 S3 的朋友都知道如果要把東西放到 S3 要先創建 S3 Bucket,在這邊我以 "static.cdk.clarence.tw" 為 S3 Bucket 名稱同時也是網址名稱來創建 因為我們是要創建靜態網頁 Bucket 所以有幾個參數需要準備:

  • 必填欄位:
    • websiteIndexDocument: 網頁的起始檔案通常都是 index.html
    • publicReadAccess: 網頁要可以 public 存取,如果不行 public 就不用看網頁了對吧 XD
  • 選填欄位:
    • websiteErrorDocument: 網頁錯誤時預設檔案名稱
  • 開發欄位:
    • removalPolicy: 填入 cdk.RemovalPolicy.DESTROY 在我們執行 destory 的時候就會順便把它移除掉比較方便 debug,不建議放在正式專案大家要記得啊!
const siteDomain = "static.cdk.clarence.tw";

const siteBucket = new s3.Bucket(this, "SiteBucket", {
  bucketName: siteDomain,
  websiteIndexDocument: "index.html",
  websiteErrorDocument: "error.html",
  publicReadAccess: true,

  removalPolicy: cdk.RemovalPolicy.DESTROY,
});

建立 ACM

因為創建 CloudFront 需要放入 ACM 因此這邊有兩個選擇

  1. 使用 acm.Certificate 創建新的 ACM
  2. 使用 Certificate.fromCertificateArn 給予 ACM 的 arn 創建 ACM 物件

1. 使用 acm.Certificate

這邊要請大家注意因為 CloudFront 只可以使用 us-east-1 的 ACM 因此如果專案不是部署在 us-east-1 就只能使用方法二了

const cert = new acm.Certificate(this, "Certificate", {
  domainName: "static.cdk.clarence.tw",
  validation: acm.CertificateValidation.fromDns(),
});

2. 使用 Certificate.fromCertificateArn

const cert = acm.Certificate.fromCertificateArn(
  this,
  "Certificate",
  "arn:aws:acm:us-east-1:888888888888:certificate/9d2306e2-420d-48df-b934-1bd21bf1ce73"
);

創建 CloudFront 並且與 S3 串接

在這邊我們使用 CloudFrontWebDistribution 來部署我們的 CloudFront 首先要注意的是 viewerCertificate 因為我們這次的範例是要使用自己的網域所以才要填這個參數,如果未來是想要使用 CloudFront 網域可以不用填,這邊還要注意的是 aliases 它本身是一個 Array 因此可以填入多個的如果有多網域需求可以在這邊增加 接下來是 s3BucketSource 填入我們的 S3 Bucket 即可

const distribution = new cloudfront.CloudFrontWebDistribution(
  this,
  "Distribution",
  {
    viewerCertificate: cloudfront.ViewerCertificate.fromAcmCertificate(
      cert,
      {
        aliases: [siteDomain],
        securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1, // default
        sslMethod: cloudfront.SSLMethod.SNI, // default
      }
    ),
    originConfigs: [
      {
        s3OriginSource: {
          s3BucketSource: siteBucket,
        },
        behaviors: [{ isDefaultBehavior: true }],
      },
    ],
  }
);

上傳打包檔案到 S3 Bucket

不知道在前面的準備 package 步驟大家有沒有發現我偷渡了一個 package 這邊有一個很好用的 package aws-s3-deployment,使用此 package 只要在我們的專案旁邊建立一個資料夾把網頁放進去剩下的事情就交給 BucketDeployment 處理就好了,這邊範例我創建了一個叫做 website-dist 的資料夾來放 Demo 網頁

new s3deploy.BucketDeployment(this, "DeployWebsite", {
  sources: [s3deploy.Source.asset("./website-dist")],
  destinationBucket: siteBucket,
  distribution,
});

測試網頁

為了方便講解我這邊也提供一個範例網頁給大家測試一下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>第 12 屆 iT 邦幫忙鐵人賽!</title>
  </head>
  <body>
    <p>用 CDK 定義 AWS 架構 AWS Lambda!</p>
  </body>
</html>

部署 CDK

第一次在 us-east-1 執行

建立 ACM 有提到 CloudFront 只能吃 us-east-1,所以特別為之前專案不是在 us-east-1 使用的朋友建立一個解說 第一次在 us-east-1 使用 CDK 的朋友需要先執行 cdk bootstrap 在這邊教大家如果只有此次專案想要執行在不同 region 可以使用 AWS_DEFAULT_REGION 這個專用的 env 來指定,後面再加上要執行的指令即可

$ AWS_DEFAULT_REGION=us-east-1 cdk bootstrap
$ AWS_DEFAULT_REGION=us-east-1 cdk deploy

如果出現

❌  CdkStaticSiteStack failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/unknown-region")

就是因為還沒執行過 cdk bootstrap

之前專案都在 us-east-1 執行

直接執行

$ cdk deploy

看一下結果吧!

https://ithelp.ithome.com.tw/upload/images/20200922/20117701O79tNUhi5M.png

今日主題教大家如何部署靜態網頁希望有幫助到大家,其實在實作這個專案的時候踩了很多雷會在下一篇分享給大家,然後還會帶大家看看 ACM 實作的方法與目前 CDK 的一些限制,希望大家會喜歡 ~

Day 7 – AWS CDK 部署 Lambda 與 API Gateway 服務 (下)

https://ithelp.ithome.com.tw/upload/images/20201024/20117701QzNO37YYln.jpg

昨天說明了 Lambda 串接 API Gateway 今天就依照昨天的內容繼續延伸吧!

以下有幾個大家可能會有的問題今天來回答他們

  • 問題一:ACM 我們之前應該就建立過了這個 ACM 想要繼續用舊的可以嗎?
  • 問題二:Lambda 除了用檔案方法部署之外我可以把 Lambda function 直接放在程式裡面嗎? 這樣就不用先上傳到 S3 了
  • 問題三:我們是不是可以在 Lambda 裡面寫我們對應的 API 程式呢?
  • 問題四:全部程式都放在同一個文件裡面好像不太好管理,可以拆開來嗎?

問題一:ACM 我們之前應該就建立過了這個 ACM 想要繼續用舊的可以嗎?

AWS CDK 印出 output

在說明如何如何使用舊的 ACM 之前先來說明如何在 CDK 印出 output 吧! 要印 output 就要使用 CfnOutput 這個 function 它是 CDK core 的基礎功能,使用方法如下:

const cert = new acm.Certificate(this, "Certificate", {
    domainName: "*.cdk.clarence.tw",
    validation: acm.CertificateValidation.fromDns(),
});
new cdk.CfnOutput(this, "ACM-ARN", {
    value: cert.certificateArn,
});

詳細說明可以看官方文件

那事不宜遲我們來印印看吧!

$ cdk deploy 
CdkLambdaStack: deploying...
[0%] start: Publishing 4570c9a06764894ceb3b53aa789576d8258408a69cfd3cf13dc7337b49bb50bd:current
[100%] success: Published 4570c9a06764894ceb3b53aa789576d8258408a69cfd3cf13dc7337b49bb50bd:current
CdkLambdaStack: creating CloudFormation changeset...


 ✅  CdkLambdaStack

Outputs:
CdkLambdaStack.AcmArn = arn:aws:acm:us-west-2:888888888888:certificate/e2f36ef1-6b06-4cb3-9128-6de273275f0d
CdkLambdaStack.Endpoint8024A810 = https://eli6fm96r2.execute-api.us-west-2.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:us-west-2:888888888888:stack/CdkLambdaStack/f95906d0-fa8f-11ea-ae89-0a43e2aa5bac

如此我們就知道 AcmArn 了

CDK 使用 Arn ID 建立 API Gateway certificate

我們來複習一下昨天建立 ACM 的方法

const cert = new acm.Certificate(this, "Certificate", {
  domainName: "*.cdk.clarence.tw",
  validation: acm.CertificateValidation.fromDns(),
});

我們來把 cert 換成用 Arn ID 來定義,假設我們的 ACM ID 為 arn:aws:acm:us-west-2:888888888888:certificate/bd476304-de15-4feb-b5f4-136fa60cae59 使用 function fromCertificateArn 定義

const cert = acm.Certificate.fromCertificateArn(
  this,
  "Certificate",
  "arn:aws:acm:us-west-2:888888888888:certificate/bd476304-de15-4feb-b5f4-136fa60cae59"
);
new cdk.CfnOutput(this, "AcmArn", {
  value: cert.certificateArn,
});

跑一下 deploy 可以看到我們上面指定的 AcmArn 與 Output 的 AcmArn是一樣的,那就是成功拉!

$ cdk deploy
CdkLambdaStack: deploying...
[0%] start: Publishing 4570c9a06764894ceb3b53aa789576d8258408a69cfd3cf13dc7337b49bb50bd:current
[100%] success: Published 4570c9a06764894ceb3b53aa789576d8258408a69cfd3cf13dc7337b49bb50bd:current
CdkLambdaStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (3/3)




 ✅  CdkLambdaStack

Outputs:
CdkLambdaStack.AcmArn = arn:aws:acm:us-west-2:888888888888:certificate/bd476304-de15-4feb-b5f4-136fa60cae59
CdkLambdaStack.Endpoint8024A810 = https://eli6fm96r2.execute-api.us-west-2.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:us-west-2:888888888888:stack/CdkLambdaStack/f95906d0-fa8f-11ea-ae89-0a43e2aa5bac

詳細說明可以看官方文件

問題二:Lambda 除了用檔案方法部署之外我可以把 Lambda function 直接放在程式裡面嗎? 這樣就不用先上傳到 S3

CDK Lambda 使用 inline code

使用 inline code 有一個 4KiB 的限制,也可能比較不好 debug,不過還是介紹給有需要使用的朋友 用法是呼叫 lambda.Code.fromInline 把 Code 轉成 String 塞進去即可,這邊為了方便閱讀所以使用了換行符號,如果是 Code 很短的朋友直接一行解決也是 OK 的呦!

const main = new lambda.Function(this, "lambda", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "index.handler",
  code: lambda.Code.fromInline(
    ' \
  exports.handler = async function (event) { \
    console.log("request:", JSON.stringify(event, undefined, 2)); \
    return { \
      statusCode: 200, \
      headers: { "Content-Type": "text/html" }, \
      body: `<meta charset="utf-8"><h1>第 12 屆 iT 邦幫忙鐵人賽 用 CDK 定義 AWS 架構 AWS Lambda!</h1>`, \
    }; \
  }; \
  '
  ),
});

Lambda 開啟 X-Ray

應該滿多使用 Lambda 的朋友會使用 X-Ray 服務的,要開啟只要把 tracing 開啟即可

const main = new lambda.Function(this, "lambda", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "index.handler",
  code: lambda.Code.fromAsset("lambda"),
  tracing: lambda.Tracing.ACTIVE,
});

問題三:我們是不是可以在 Lambda 裡面寫我們對應的 API 程式呢?

為了好解說我們把 API 分成幾個不一樣的 path:

  • root path 顯示:第 12 屆 iT 邦幫忙鐵人賽!
  • ironman path 顯示:用 CDK 定義 AWS 架構 AWS Lambda!
  • 其他頁面:跳轉到我的 Blog

修改一下 Lambda Function

完成以上條件的程式可以這樣寫,我們修改一下 lambda/index.js

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  switch (event.path) {
    case "/":
      switch (event.httpMethod) {
        case "GET":
          return {
            statusCode: 200,
            headers: { "Content-Type": "text/html" },
            body: `<meta charset="utf-8"><h1>第 12 屆 iT 邦幫忙鐵人賽!</h1>`,
          };
      }
    case "/ironman":
      switch (event.httpMethod) {
        case "GET":
          return {
            statusCode: 200,
            headers: { "Content-Type": "text/html" },
            body: `<meta charset="utf-8"><h1>用 CDK 定義 AWS 架構 AWS Lambda!</h1>`,
          };
      }
    default:
      return {
        statusCode: 301,
        headers: {
          Location: "https://blog.clarence.tw/",
        },
      };
  }
};

執行一下 cdk deploy,看一下結果 查看 https://api.cdk.clarence.tw/ https://ithelp.ithome.com.tw/upload/images/20200920/2011770118IMSg6yzx.png

查看 https://api.cdk.clarence.tw/ironman https://ithelp.ithome.com.tw/upload/images/20200920/20117701fu5Ww1gLxF.png

其他頁面因為會直接跳轉所以我們用 curl 來表示

$ curl -sID -X https://api.cdk.clarence.tw/blog
HTTP/2 301
date: Sun, 20 Sep 2020 15:56:34 GMT
content-type: application/json
content-length: 0
location: https://blog.clarence.tw/
x-amzn-requestid: c3e2f2d3-6db0-4f81-be69-3fce13a9957f
x-amz-apigw-id: TLBD9FYePHcF7QA=
x-amzn-trace-id: Root=1-5f677bb2-360d545301670034714adbfe;Sampled=0

問題四:全部程式都放在同一個文件裡面好像不太好管理,可以拆開來嗎?

使用 API Gateway 接 Lambda

介紹了這麼多單個 Lambda 寫 API 的方法,現在來說明怎麼把 Lambda 拆開的方法

準備多個 Lambda Function

修改一下原本的 lambda/index.js 讓我們比較好辨識目前的網頁

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/html" },
    body: `<meta charset="utf-8"><h1>第 12 屆 iT 邦幫忙鐵人賽!</h1>`,
  };
};

新增一個新的 Lambda Function lambda/ironmanFunc.js 來增加頁面

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/html" },
    body: `<meta charset="utf-8"><h1>用 CDK 定義 AWS 架構 AWS Lambda!</h1>`,
  };
};

修改 cdk-lambda-stack.js

原本的 CDK stack Lambda Function 只有定義一個,現在我們再多新增一個 ironmanFunc 第 28 行:使用 GET Method 串接到原本的 main function 第 29 行:新增一個 12th 的 path 第 30 行:使用 GET method 串接新的 ironmanFunc

const main = new lambda.Function(this, "lambda", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "index.handler",
  code: lambda.Code.fromAsset("lambda"),
});
const ironmanFunc = new lambda.Function(this, "ironmanFunc", {
  runtime: lambda.Runtime.NODEJS_10_X,
  handler: "ironmanFunc.handler",
  code: lambda.Code.fromAsset("lambda"),
});

const cert = acm.Certificate.fromCertificateArn(
  this,
  "Certificate",
  "arn:aws:acm:us-west-2:888888888888:certificate/bd476304-de15-4feb-b5f4-136fa60cae59"
);
new cdk.CfnOutput(this, "AcmArn", {
  value: cert.certificateArn,
});
const api = new apigw.LambdaRestApi(this, "Endpoint", {
  handler: main,
  domainName: {
    domainName: "api.cdk.clarence.tw",
    certificate: cert,
  },
  proxy: false,
});
api.root.addMethod("GET", new apigw.LambdaIntegration(main));
const ironman = api.root.addResource("12th");
ironman.addMethod("GET", new apigw.LambdaIntegration(ironmanFunc));

deploy 一下測試一下效果

先到 https://api.cdk.clarence.tw/ 看一下 https://ithelp.ithome.com.tw/upload/images/20200920/20117701tzHjED0Xbd.png

再到 https://api.cdk.clarence.tw/12th 果然如預期 https://ithelp.ithome.com.tw/upload/images/20200920/20117701u8BlMNaPH8.png

今天說明的 CDK 使用 Lambda、API Gateway 的延伸,希望對大家都有收穫,我們明天再見 ~

Day 6 – AWS CDK 部署 Lambda 與 API Gateway 服務 (上)

今天我們來部署一個 API Gateway 串接 Lambda 的服務,讓我們直接來 serverless 一下!

https://ithelp.ithome.com.tw/upload/images/20201024/20117701QzNO37YYln.jpg

建立 Lambda

創建新的 CDK Project

我們這次的專案叫它 cdk-lambda,首先我們先創建資料夾然後 init 一下專案

$ mkdir cdk-lambda && cd cdk-lambda
$ cdk init --language typescript
Applying project template app for typescript
# Welcome to your CDK TypeScript project!

This is a blank project for TypeScript development with CDK.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

Initializing a new git repository...
Executing npm install...
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated [email protected]: Please see https://github.com/lydell/urix#deprecated
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No repository field.
npm WARN [email protected] No license field.

✅ All done!

與之前的 CDK Project 創建方法做比較

可以看到這次沒有加入 template 指令,所以在 cdk-lambda.stack.ts 裡面沒有內容只有一行註解,移除註解就可以開始我們今天的主題了!

https://ithelp.ithome.com.tw/upload/images/20200919/20117701GbnkomaadV.png

創建 Lambda function 主程式

  • 接下來我們創建一個放 lambda function 的資料夾
$ mkdir lambda
  • 放入 lambda/index.js 測試檔案
exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return;
};
  • 加入 cdk-lambda.stack.ts 放入 lambda
import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";

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

    const main = new lambda.Function(this, "lambda", {
      runtime: lambda.Runtime.NODEJS_10_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset("lambda"),
    });
  }
}
  • 安裝套件
$ npm i @aws-cdk/aws-lambda
  • 執行 deploy
$ cdk deploy
  • 開啟 AWS Console 檢查一下看一下我們的 Lambda 是否有成功部署上去! https://ithelp.ithome.com.tw/upload/images/20200920/20117701o9dCUQzGvW.png

準備 API Gateway 要用的 Lambda Function

第一次使用 CDK 部署 Lambda 來測試看看改變我們的 Lambda Function 會不會同步幫我們修改吧! 此次的修改內容因應要串接 API Gateway 做了一點改變 如果想要進一步知道其他的串接參數可以直接閱讀官方文件在 API Gateway 中設定整合回應

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/html" },
    body: `<h1>第 12 屆 iT 邦幫忙鐵人賽 用 CDK 定義 AWS 架構 AWS Lambda!</h1>`,
  };
};

在部署之前先查看一下 cdk diff 可以發現我們更新了 index.js CDK 會幫我們重新上傳檔案到 S3 並且更新它 這段繁瑣的功能 CDK 完美的幫我們處理好了 ~~ 可以從 Parameters 與 Resources 看到有 S3 的新增檔案與移除

$ cdk diff

因為剛開始使用我們還是重新整理一下 AWS Console 看看吧! https://ithelp.ithome.com.tw/upload/images/20200920/201177010v7nnBdvdN.png

加入 API Gateway

修改 lib/cdk-lambda-stack.ts

我絕對不會說我本來是想要串 ALB 可是突然想到要先講 VPC 才可以創 ALB 因此決定還是先寫 API Gateway 要使用 API Gateway 串接 Lambda 可以直接使用 CDK 的 LambdaRestApi 來完成 是不是非常簡單又快速呢!這邊如果手動可是還要在 AWS Console 點個好幾下呢!

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigw from '@aws-cdk/aws-apigateway';

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

    const main = new lambda.Function(this, "lambda", {
      runtime: lambda.Runtime.NODEJS_10_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset("lambda"),
    });
    new apigw.LambdaRestApi(this, 'Endpoint', {
      handler: main
    });
  }
}

檢查一下 diff 可以看到新增了 API Gateway 的 Resources

$ cdk diff

安裝套件

$ npm i @aws-cdk/aws-apigateway

使用部署指令完成後可以看到網頁了!

$ cdk deploy

部署完成後測試

網頁亂碼

我們來看一下網頁,哇!竟然是亂碼,其實是我們忘記指定它是 UTF-8 了來修正一下程式 https://ithelp.ithome.com.tw/upload/images/20200920/2011770168RYYmfbLl.png

修正網頁

其實這邊的問題主要是在 HTML 語法的部分跟 Lambda 比較沒有關係,不過還是介紹一下 XD

exports.handler = async function (event) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/html" },
    body: `<meta charset="utf-8"><h1>第 12 屆 iT 邦幫忙鐵人賽 用 CDK 定義 AWS 架構 AWS Lambda!</h1>`,
  };
};

再部署一次就正常了 https://ithelp.ithome.com.tw/upload/images/20200920/201177018WFxePIQre.png

讓 API Gateway 支援 customer domain

目前使用的網址是 API Gateway 的 domain 覺得不太美觀來幫它換一下! 首先我們先在 LambdaRestApi 指定 domain name 如 21-24 行使用 api.cdk.clarence.tw 在 API Gateway 使用 Customer Domain 需要使用 HTTPS 因此還需要設定 ACM 來新增 ACM 使用 *.cdk.clarence.tw 如 15-18 行

import * as cdk from "@aws-cdk/core";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigw from "@aws-cdk/aws-apigateway";
import * as acm from "@aws-cdk/aws-certificatemanager";

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

    const main = new lambda.Function(this, "lambda", {
      runtime: lambda.Runtime.NODEJS_10_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset("lambda"),
    });
    const cert = new acm.Certificate(this, "Certificate", {
      domainName: "*.cdk.clarence.tw",
      validation: acm.CertificateValidation.fromDns(),
    });
    new apigw.LambdaRestApi(this, "Endpoint", {
      handler: main,
      domainName: {
        domainName: "api.cdk.clarence.tw",
        certificate: cert,
      },
    });
  }
}

安裝套件

$ npm i @aws-cdk/aws-certificatemanager

修改完程式後我們來更新一下

$ cdk deploy

執行完成後到 AWS Console 看 API Gateway/Custom domain names 看一下是否設定完成 https://ithelp.ithome.com.tw/upload/images/20200920/20117701HnnsTzBIuz.png

確定完成後到我們的 DNS 設定一下 Domain 的 CNAME,接下來就可以使用新的網址打開網站拉! 是不是變得比較美觀 ~ https://ithelp.ithome.com.tw/upload/images/20200920/20117701cOXKj3bPP0.png

以上是今天的 CDK 使用 Lambda、API Gateway 與 ACM 解說,目前的排版可能有點亂我還在思考要怎麼做會比較好看請大家見諒 QQ

Day 5 – 執行 AWS CDK sample-app

昨天分析完了整個 sample-app 程式,今天就直接來執行它吧!

https://ithelp.ithome.com.tw/upload/images/20201024/20117701l2JAbJK5oH.jpg

今日的主題除了教大家怎麼把 sample-app 部署到 AWS 上面之外還會教大家怎麼簡單的修改 CDK 程式先體驗 CDK 的方便之處,那就來看看今天的內容吧!

執行 CDK sample-app

cdk bootstrap

第一次使用 CDK 先執行 bootstrap

如果之前有做過了就不需要再做了呦!

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://888888888888/us-west-2...
 ✅  Environment aws://888888888888/us-west-2 bootstrapped (no changes).

cdk synth

看一下部署腳本 一開始使用 CDK 還是可以先看一下如果使用 CloudFormation 會怎麼寫的,看完就會這樣寫真的滿辛苦的

$ cdk synth
Resources:
  HelloCdkQueueB56C77B9:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/Resource
  HelloCdkQueuePolicy027FC30A:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
              ArnEquals:
                aws:SourceArn:
                  Ref: HelloCdkTopic1F583424
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Resource:
              Fn::GetAtt:
                - HelloCdkQueueB56C77B9
                - Arn
        Version: "2012-10-17"
      Queues:
        - Ref: HelloCdkQueueB56C77B9
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/Policy/Resource
  HelloCdkQueueHelloCdkStackHelloCdkTopic850E0FBD36A066B9:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn:
        Ref: HelloCdkTopic1F583424
      Endpoint:
        Fn::GetAtt:
          - HelloCdkQueueB56C77B9
          - Arn
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/HelloCdkStackHelloCdkTopic850E0FBD/Resource
  HelloCdkTopic1F583424:
    Type: AWS::SNS::Topic
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkTopic/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=1.63.0,@aws-cdk/aws-cloudwatch=1.63.0,@aws-cdk/aws-iam=1.63.0,@aws-cdk/aws-kms=1.63.0,@aws-cdk/aws-sns=1.63.0,@aws-cdk/aws-sns-subscriptions=1.63.0,@aws-cdk/aws-sqs=1.63.0,@aws-cdk/cloud-assembly-schema=1.63.0,@aws-cdk/core=1.63.0,@aws-cdk/cx-api=1.63.0,@aws-cdk/region-info=1.63.0,jsii-runtime=node.js/v12.16.3
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2

cdk deploy

開始執行部屬,執行此指令如果有修改權限都會再一次跟使用者確定修改的內容,確定請按 y

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬───────────────────┬────────┬───────────────────┬───────────────────┬─────────────────────┐
│   │ Resource          │ Effect │ Action            │ Principal         │ Condition           │
├───┼───────────────────┼────────┼───────────────────┼───────────────────┼─────────────────────┤
│ + │ ${HelloCdkQueue.A │ Allow  │ sqs:SendMessage   │ Service:sns.amazo │ "ArnEquals": {      │
│   │ rn}               │        │                   │ naws.com          │   "aws:SourceArn":  │
│   │                   │        │                   │                   │ "${HelloCdkTopic}"  │
│   │                   │        │                   │                   │ }                   │
└───┴───────────────────┴────────┴───────────────────┴───────────────────┴─────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?  y
HelloCdkStack: deploying...
[██████████████████████████████████████████████████████████] (6/6)




 ✅  HelloCdkStack

Stack ARN:
arn:aws:cloudformation:us-west-2:888888888888:stack/HelloCdkStack/cbb80510-f9d0-11ea-b44e-0a8a148431ae

文字版

https://ithelp.ithome.com.tw/upload/images/20200921/20117701cfOJDLEjrf.png

圖片版

查看 CloudFormation 結果

在這邊可以很清楚的看到上面的 Stack ARN 與我們的 Stack ID 一樣,並且有部署成功 https://ithelp.ithome.com.tw/upload/images/20200919/20117701QYoa1USu1T.png

再來檢查一下 Resources 的部分 https://ithelp.ithome.com.tw/upload/images/20200919/20117701pvf2lZyz2S.png

到 SNS 看看新建的 Topic https://ithelp.ithome.com.tw/upload/images/20200919/20117701GU15q1SGFC.png

到 SQS 檢查 timeout 為 300 秒也就是 5 分鐘,並且訂閱了 SNS https://ithelp.ithome.com.tw/upload/images/20200919/20117701gvEhDKgfgJ.png

修改一下 hello-cdk-stack.ts

體驗一下如果把某些程式註解是否會真的幫我們移除對應的服務,把 13 ~ 16 行註解

import * as sns from "@aws-cdk/aws-sns";
import * as subs from "@aws-cdk/aws-sns-subscriptions";
import * as sqs from "@aws-cdk/aws-sqs";
import * as cdk from "@aws-cdk/core";

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

    const queue = new sqs.Queue(this, "HelloCdkQueue", {
      visibilityTimeout: cdk.Duration.seconds(300),
    });

    // const topic = new sns.Topic(this, 'HelloCdkTopic');

    // topic.addSubscription(new subs.SqsSubscription(queue));
  }
}

跑一下 cdk diff

執行一下 cdk diff 可以很清楚的看到什麼 Resources 被移除了

  • [-] AWS::SQS::QueuePolicy HelloCdkQueuePolicy027FC30A destroy
  • [-] AWS::SNS::Subscription HelloCdkQueueHelloCdkStackHelloCdkTopic850E0FBD36A066B9 destroy
  • [-] AWS::SNS::Topic HelloCdkTopic1F583424 destroy
$ cdk diff
Stack HelloCdkStack
IAM Statement Changes
┌───┬───────────────────┬────────┬───────────────────┬───────────────────┬─────────────────────┐
│   │ Resource          │ Effect │ Action            │ Principal         │ Condition           │
├───┼───────────────────┼────────┼───────────────────┼───────────────────┼─────────────────────┤
│ - │ ${HelloCdkQueue.A │ Allow  │ sqs:SendMessage   │ Service:sns.amazo │ "ArnEquals": {      │
│   │ rn}               │        │                   │ naws.com          │   "aws:SourceArn":  │
│   │                   │        │                   │                   │ "${HelloCdkTopic1F5 │
│   │                   │        │                   │                   │ 83424}"             │
│   │                   │        │                   │                   │ }                   │
└───┴───────────────────┴────────┴───────────────────┴───────────────────┴─────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::SQS::QueuePolicy HelloCdkQueuePolicy027FC30A destroy
[-] AWS::SNS::Subscription HelloCdkQueueHelloCdkStackHelloCdkTopic850E0FBD36A066B9 destroy
[-] AWS::SNS::Topic HelloCdkTopic1F583424 destroy

文字版

https://ithelp.ithome.com.tw/upload/images/20200921/20117701F4ML0Q8SMv.png

圖片版

再跑一次 cdk deploy

看完之後我們來執行一次 cdk deploy,等執行成功就來檢查一下

$ cdk deploy
HelloCdkStack: deploying...
HelloCdkStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (5/5)



 ✅  HelloCdkStack

Stack ARN:
arn:aws:cloudformation:us-west-2:888888888888:stack/HelloCdkStack/cbb80510-f9d0-11ea-b44e-0a8a148431ae

檢查一下 CloudFormation

可以發現剩下一個 SQS,代表有執行成功! https://ithelp.ithome.com.tw/upload/images/20200919/20117701Ekpmvfqodm.png

重新整理一下 SNS

剛剛的 SNS 也已經被移除掉了 https://ithelp.ithome.com.tw/upload/images/20200919/20117701ILg8dPhzct.png

檢查一下 SQS

確定沒有 SNS 的訂閱資料 https://ithelp.ithome.com.tw/upload/images/20200919/20117701hNWhnENec3.png

最後我們把整個環境移除掉吧!

今天的講解差不多結束了,我們來把整個 cdk destroy 掉 執行會出現警告直接按下 y 即可

$ cdk destroy
Are you sure you want to delete: HelloCdkStack (y/n)? y
HelloCdkStack: destroying...
9:20:24 AM | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack
9:20:26 AM | DELETE_IN_PROGRESS   | AWS::SQS::Queue    | HelloCdkQueue

 ✅  HelloCdkStack: destroyed

文字版

https://ithelp.ithome.com.tw/upload/images/20200921/20117701uJnbCaDfrt.png

圖片版

搜尋一下剛剛的 CloudFormation

可以發現已經沒有任何東西拉! https://ithelp.ithome.com.tw/upload/images/20200919/201177016h9RkzmIau.png

以上是 AWS CDK sample-app 的執行與測試

Day 4 – 分析 AWS CDK sample-app

昨天說明完了指令的用法,今天來講解一下我們建立的 sample-app 裡面藏了什麼內容吧!

如果直接跳到這篇的同學可以直接使用 cdk init sample-app --language=typescript 跑出範例程式或是到 Day 2 – 第一個 AWS CDK 專案與參考資源分享 補一下內容呦!

今日內容主要說明怎麼使用 typescript 建立一個 CDK 應用與從頭到尾讀懂一隻基礎的 typescript 程式

進入點載入位置

先看到 bin/hello-cdk.ts

#!/usr/bin/env node
import * as cdk from '@aws-cdk/core';
import { HelloCdkStack } from '../lib/hello-cdk-stack';

const app = new cdk.App();
new HelloCdkStack(app, 'HelloCdkStack');

程式講解

第 1 行

#!/usr/bin/env node

代表此文件使用 node 執行,那為什麼是這樣寫呢? 這就要從 #! 這個語法說起了,先說明它的名字叫做 Shebang 或是 Hashbang 通常 # 稱它為 sharp 而 ! 稱它為 bang 如此就知道為什麼是這個名字了吧! 此語法用於直接呼叫直譯器,說到這邊應該會有一個疑問竟然如此不是應該寫作?

#!/usr/bin/node

這樣其實不太完整以我現在使用的 MAC 來說

$ /usr/bin/node
zsh: no such file or directory: /usr/bin/node

會找不到 node 的位置那這時候就應該使用 env 指令執行它會去找使用者的 PATH 設定檔在再去執行 node

$ env node
Welcome to Node.js v14.8.0.
Type ".help" for more information.
>

第 2 ~ 3 行

引入我們的 Library

import * as cdk from '@aws-cdk/core';
import { HelloCdkStack } from '../lib/hello-cdk-stack';

第 5 行

使用 new 將物件建構起來

const app = new cdk.App();

第 6 行

把剛剛 new 起來的 cdk.App() 放入並取名 HelloCdkStack 未來後面會常常看到這樣的寫法

new HelloCdkStack(app, 'HelloCdkStack');

主要程式位置

看到 lib/hello-cdk-stack.ts

import * as sns from '@aws-cdk/aws-sns';
import * as subs from '@aws-cdk/aws-sns-subscriptions';
import * as sqs from '@aws-cdk/aws-sqs';
import * as cdk from '@aws-cdk/core';

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

    const queue = new sqs.Queue(this, 'HelloCdkQueue', {
      visibilityTimeout: cdk.Duration.seconds(300)
    });

    const topic = new sns.Topic(this, 'HelloCdkTopic');

    topic.addSubscription(new subs.SqsSubscription(queue));
  }
}

第 6 行

export 一個 class 名稱 HelloCdkStack 繼承 cdk.Stack

export class HelloCdkStack extends cdk.Stack {}

第 7 行

透過 new 傳入的參數用 constructor 接收

constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {}

第 8 行

使用 super 得到父類別的值

super(scope, id, props);

第 10 ~ 12 行

新建一個 sqs queue 並且指定為 queue 設定 timeout 為 300 秒

const queue = new sqs.Queue(this, 'HelloCdkQueue', {
    visibilityTimeout: cdk.Duration.seconds(300)
});

第 14 行

新建一個 sns topic 並且指定為 topic

const topic = new sns.Topic(this, 'HelloCdkTopic');

第 16 行

把 sns topic 加入到 sqs queue 中

topic.addSubscription(new subs.SqsSubscription(queue));

以上是 CDK sample-app 的介紹如果有錯我會盡快修正的,謝謝大家!

明日的內容會說明如何把今日的 sample-app 部署上去 AWS,如果是在等如何部署的讀者就請在等我一天!

Day 3 – AWS CDK 指令介紹

昨天講解了專案的創建今天來解釋基礎的 AWS CDK 指令,不過在講解之前應該要先跟大家說明怎麼使用 AWS CLI 不然在講解 AWS CDK 指令的時候有的指令可能會卡住

https://ithelp.ithome.com.tw/upload/images/20201024/20117701EhRLq1IeOZ.jpg

AWS CLI 安裝

使用 msi 安裝

首先先說明 Windows 的安裝方法,我個人覺得 Windows 是裡面最好安裝的只要到官方下載安裝檔即可 安裝後測試一下指令

C:\> aws --version
aws-cli/2.0.36 Python/3.7.4 Windows/10 botocore/2.0.0

詳細介紹可以參考官方安裝文件

Mac 安裝 AWS CLI

使用 brew 安裝

我通常在 Mac 上面盡可能指令都交給 brew 管理,所以我首推使用 brew 安裝指令,不過這不是官方推薦的方法,但是我比較喜歡

$ brew install awscli

使用 brew 在舊版 awscli@1 (a.k.a. AWS CLI version 1) 會有一個不太方便的地方是如果有改版內容想要先使用就算發 PR 也沒辦法被 merge 因為有規定 10 版做為一個單位

awscli should only be updated every 10 releases on multiples of 10

homebrew-core/Formula/[email protected]

新版的 awscli 就沒有這項規定了 homebrew-core/Formula/awscli.rb

使用 pkg 安裝

官方推薦的方法是使用 pkg 安裝,基本上安裝方法就是跟著安裝精靈走就好,其實也是滿方便的就看個人喜歡的方法囉

詳細介紹可以參考官方安裝文件

設定 AWS CLI

基礎 AWS configure 設定

安裝完後先來做 configure 設定,通常應該只有一個 AWS 帳號就直接使用如下設定就

$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-west-2
Default output format [None]: 

AWS configure profile 設定

不過有的人可能因為工作關係有很多 AWS Account 總不可能每次都去挖自己的 Access Key,所以指令有提供 profile 的方法,感覺就像是多使用者的概念之後下指令只要把 profile 帶在後面就可以成功使用了

$ aws configure --profile ithome
AWS Access Key ID [None]: AKIAI44QH8DHBEXAMPLE
AWS Secret Access Key [None]: je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
Default region name [None]: us-east-1
Default output format [None]: 

今天想要列出所有 ithome 這個 profile 的 S3 bucket 就可以使用

$ aws s3 ls --profile ithome

其他功能等後面需要再介紹給大家

AWS CDK Toolkit command

cdk init

昨天有介紹過 cdk init 指令了,不過還沒提到的是 init 除了 sample-app 的 Template 還有 applib,而如果不想要 Template 其實也可以直接執行 cdk init --language=typescript 就會是一個空專案了

$ cdk init --list
Available templates:
* app: Template for a CDK Application
   └─ cdk init app --language=[csharp|fsharp|java|javascript|python|typescript]
* lib: Template for a CDK Construct Library
   └─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
   └─ cdk init sample-app --language=[csharp|fsharp|java|javascript|python|typescript]

cdk list

列出所有的 stack list,而為什麼以目前的範例來說會出現 HelloCdkStack 呢? 其實是因為我們的 bin/hello-cdk.ts 定義的,如果覺得指令太長可以改用 cdk ls

$ cdk list
HelloCdkStack

如果想要看更詳細的 stack 資料可以使用

$ cdk list --long
- id: HelloCdkStack
  name: HelloCdkStack
  environment:
    account: unknown-account
    region: unknown-region
    name: aws://unknown-account/unknown-region

cdk synthesize

看看目前程式產生的 CloudFormation 腳本可以直接使用這個指令,它的短指令是 cdk synth

$ cdk synth HelloCdkStack
Resources:
  HelloCdkQueueB56C77B9:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/Resource
  HelloCdkQueuePolicy027FC30A:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
              ArnEquals:
                aws:SourceArn:
                  Ref: HelloCdkTopic1F583424
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Resource:
              Fn::GetAtt:
                - HelloCdkQueueB56C77B9
                - Arn
        Version: "2012-10-17"
      Queues:
        - Ref: HelloCdkQueueB56C77B9
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/Policy/Resource
  HelloCdkQueueHelloCdkStackHelloCdkTopic850E0FBD36A066B9:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn:
        Ref: HelloCdkTopic1F583424
      Endpoint:
        Fn::GetAtt:
          - HelloCdkQueueB56C77B9
          - Arn
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkQueue/HelloCdkStackHelloCdkTopic850E0FBD/Resource
  HelloCdkTopic1F583424:
    Type: AWS::SNS::Topic
    Metadata:
      aws:cdk:path: HelloCdkStack/HelloCdkTopic/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=1.63.0,@aws-cdk/aws-cloudwatch=1.63.0,@aws-cdk/aws-iam=1.63.0,@aws-cdk/aws-kms=1.63.0,@aws-cdk/aws-sns=1.63.0,@aws-cdk/aws-sns-subscriptions=1.63.0,@aws-cdk/aws-sqs=1.63.0,@aws-cdk/cloud-assembly-schema=1.63.0,@aws-cdk/core=1.63.0,@aws-cdk/cx-api=1.63.0,@aws-cdk/region-info=1.63.0,jsii-runtime=node.js/v12.16.3
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2

如果想要把目前的 CloudFormation templates 輸出成 yaml 也可以直接使用 cdk synth > output.yaml 或是以前是寫 JSON 的朋友也可以讓它輸出 JSON cdk synth --json > output.json 是不是很方便啊,不過我們現在使用 CDK 就是為了不想直接跟 CloudFormation 打交道就看看壞念一下吧 ~

cdk bootstrap

此指令是第一次要執行 cdk 部署前要執行的指令,它會在 S3 開啟一個 bucket 裡面會放有需要執行 CloudFormation 必要的東西

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://888888888888/us-west-2...
 ✅  Environment aws://888888888888/us-west-2 bootstrapped (no changes).

cdk deploy

部署 CDK 腳本指令未來會常常用到它,詳細的使用方法等下一章再來說明

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬───────────────────┬────────┬───────────────────┬───────────────────┬─────────────────────┐
│   │ Resource          │ Effect │ Action            │ Principal         │ Condition           │
├───┼───────────────────┼────────┼───────────────────┼───────────────────┼─────────────────────┤
│ + │ ${HelloCdkQueue.A │ Allow  │ sqs:SendMessage   │ Service:sns.amazo │ "ArnEquals": {      │
│   │ rn}               │        │                   │ naws.com          │   "aws:SourceArn":  │
│   │                   │        │                   │                   │ "${HelloCdkTopic}"  │
│   │                   │        │                   │                   │ }                   │
└───┴───────────────────┴────────┴───────────────────┴───────────────────┴─────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

文字版

https://ithelp.ithome.com.tw/upload/images/20200921/20117701pH38JyvBNI.png

圖片版

cdk destroy

因為 CDK 底層是 CloudFormation 的關係如果我們想要移除部署好的東西也可以輕易的把它移除的乾乾淨淨,只要使用這個指令即可達成

$ cdk destroy
Are you sure you want to delete: HelloCdkStack (y/n)? y
HelloCdkStack: destroying...

 ✅  HelloCdkStack: destroyed

cdk diff

在 CDK 裡面有一個很好用的功能就是 diff 我們可以輕易的知道現在的版本與已經部署的版本差異是什麼,只要使用 cdk diff 即可清楚的知道,以下面來說我們此範例會在 IAM 新增一個 Action sqs:SendMessage 與新增 4 個 Resources

$ cdk diff
IAM Statement Changes
┌───┬───────────────────┬────────┬───────────────────┬───────────────────┬─────────────────────┐
│   │ Resource          │ Effect │ Action            │ Principal         │ Condition           │
├───┼───────────────────┼────────┼───────────────────┼───────────────────┼─────────────────────┤
│ + │ ${HelloCdkQueue.A │ Allow  │ sqs:SendMessage   │ Service:sns.amazo │ "ArnEquals": {      │
│   │ rn}               │        │                   │ naws.com          │   "aws:SourceArn":  │
│   │                   │        │                   │                   │ "${HelloCdkTopic}"  │
│   │                   │        │                   │                   │ }                   │
└───┴───────────────────┴────────┴───────────────────┴───────────────────┴─────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Conditions
[+] Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::SQS::Queue HelloCdkQueue HelloCdkQueueB56C77B9 
[+] AWS::SQS::QueuePolicy HelloCdkQueue/Policy HelloCdkQueuePolicy027FC30A 
[+] AWS::SNS::Subscription HelloCdkQueue/HelloCdkStackHelloCdkTopic850E0FBD HelloCdkQueueHelloCdkStackHelloCdkTopic850E0FBD36A066B9 
[+] AWS::SNS::Topic HelloCdkTopic HelloCdkTopic1F583424 

文字版

https://ithelp.ithome.com.tw/upload/images/20200921/201177010nlP7lYF81.png

圖片版

cdk docs

如果想要快速的尋找文件就可以使用它,它會幫我們開啟瀏覽器並且帶我們到 https://docs.aws.amazon.com/cdk/api/latest/ 其實很方便 https://ithelp.ithome.com.tw/upload/images/20200918/201177012kpXQSWhy6.png

cdk doctor

如果有 bug 需要 report 的時候會需要指令此指令它會收集目前的 env 與 CDK 版本資訊

$ cdk doctor
ℹ️ CDK Version: 1.63.0 (build 7a68125)
ℹ️ AWS environment variables:
  - AWS_STS_REGIONAL_ENDPOINTS = regional
  - AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  - AWS_SDK_LOAD_CONFIG = 1
ℹ️ No CDK environment variables

以上是 CDK 指令的介紹如果有錯歡迎跟我說我會盡快修正的,謝謝大家! 今天的內容就到此,明天會來講解

Day 2 – 第一個 AWS CDK 專案與參考資源分享

昨天談完了為什麼想要學習 AWS CDK,今天就來建立屬於自己的第一個 AWS CDK 專案吧! 建立完成後跟大家談談學習 AWS CDK 有什麼資源可以看吧!

https://ithelp.ithome.com.tw/upload/images/20201022/20117701YfzE0FyUyP.jpg

建立我的第一個 AWS CDK 專案

安裝指令

  • 確認 NodeJS
$ node -v
v14.8.0

Node.js 版本需求 ≥ 10.13.0

  • 確認 NPM
$ npm -v
6.14.8
$ npm i -g aws-cdk
  • 建立專案資料夾
$ mkdir hello-cdk && cd hello-cdk
  • 初始化專案
$ cdk init sample-app --language=typescript

Applying project template sample-app for typescript
# Welcome to your CDK TypeScript project!

You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`HelloCdkStack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

Initializing a new git repository...
Executing npm install...
npm WARN deprecated [email protected]: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated [email protected]: Please see https://github.com/lydell/urix#deprecated
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No repository field.
npm WARN [email protected] No license field.

✅ All done!

指令詳細介紹:

  • 建立專案:cdk init sample-app –language=typescript
  • 初始化模板 sample-app:cdk init sample-app –language=typescript
  • 語言 typescript:sample-app cdk init sample-app –language=typescript

開啟專案

$ code .

https://ithelp.ithome.com.tw/upload/images/20200917/20117701Yh3Af7atRI.png

建議安裝 AWS Toolkit https://ithelp.ithome.com.tw/upload/images/20200917/20117701ecG9hcHP1W.png

資料夾檔案介紹

https://ithelp.ithome.com.tw/upload/images/20200917/201177016yYXkWkYSu.png

  • lib/cdk-workshop-stack.ts:CDK 主要的程式位置
  • bin/cdk-workshop.ts:主要的程式進入點預設會引用 lib/cdk-workshop-stack.ts
  • package.json:npm 模組清單裡面定義了套件的版本與指令,例如:buildwatchtestcdk
  • cdk.json:告訴 toolkit 如何執行 app
  • tsconfig.json:typescript 設定檔
  • .gitignore:告訴 git 應該要排除的文件
  • .npmignore:告訴 npm 應該要排除的文件
  • node_modules:nodejs 套件包執行完 npm install 後的文件都會安裝在此資料夾裡面
  • test:CDK 測試的程式位置

參考資源

  • AWS 文件 首先要跟大家說明的是 AWS 文件,文件會從 What is the AWS CDK? 開始介紹,目前只有英文介紹可能會比較深澀一點,後面的文章如果有需要會引用裡面的內容來說明

  • AWS CDK Workshop AWS CDK Workshop 內容是一個 Workshop 會從 Lambda、API Gateway、DynamoDB、CloudWatch 一步步教你如何做出一個簡單的 Web 服務,最後會說明怎麼把剛剛建立的服務給移除

  • AWS CDK Github AWS CDK 為一個 Open Source Project 因此所有的程式碼都可以從這邊找到,如果遇到了問題也可以直接發 issue 給官方詢問,有新的想法或是 BUG 修復也可以直接發送 PR

  • Pahud Dev Youtube 頻道 AWS CDK 影片目前有 11 支 4K 高畫質影片不是文字派就看影片吧! Pahud – Developer Advocate – AWS

Day 1 – 一起學習 AWS Cloud Development Kit (AWS CDK)

https://ithelp.ithome.com.tw/upload/images/20201019/20117701PWxRmGljdw.jpg

前言

開賽第一天先來跟大家聊聊為什麼想要學習 AWS Cloud Development Kit (AWS CDK),如果用過 AWS 的使用者應該都知道如果要在 AWS 建構一個雲端基礎架構(IaC)有以下兩種方法

  1. 手動建立
  2. AWS CloudFormation

手動建立

這是最基礎的方法,相信大家跟我一樣一開始都是使用這個方法學習 AWS 的,使用這個方法沒有問題,不過問題是如果今天收到的需求是一次建立 10 台 EC2 並且都是不一樣 type 的 EC2 相信會是一個按到手酸的節奏,而且不小心手滑可能會建立錯或是忘記自己建立到哪一台就是一個一直做重複動作的節奏

AWS CloudFormation

如果使用這個方法可以先寫好腳本並且可以在部署前先 Review,基本上就可以解決上面提出的問題了,不過有個美中不足是 CloudFormation 腳本寫起來本身不太好閱讀,因為他就是一串 JSON 或是 YAML,如果有寫過就知道有時候寫一寫會不清楚自己在寫什麼,而且還沒有註解功能 (╥﹏╥)

簡單介紹 AWS CDK

https://ithelp.ithome.com.tw/upload/images/20200916/201177010TWDfhzFsh.png

為了解決這個問題 AWS 就在 2019-07-11 發佈了第一版的 AWS 雲端開發套件 (AWS CDK),也就實作了基礎架構即代碼(IaC)讓 CloudFormation 可以用程式碼來寫解決了很多問題像是

  • 可以撰寫測試腳本
  • 不用定義繁瑣的 AWS IAM (Identity and Access Management)
  • 目前的部署腳本與上一版先做差異分析
  • 如果有服務 ID 需要給予下一個服務也可以直接處理

文章目錄

以下是我 30 天鐵人賽文章的總整理,如果沒時間一篇一篇看可以直接挑自己喜歡的主題下去看: