20201112のAWSに関する記事は24件です。

AWS SDK for RubyでS3オブジェクトサイズの取得

HLS形式の動画サイズ取得の為に使いました!

※オブジェクトデータ1000件以上を想定しています。

Class: Aws::S3::Client

client = AWS::S3::new

size = 0
options = { bucket: [バケット名], prefix: [プレフィックス] }
loop do
  object_list = client.list_objects_v2(options)
  object_list.contents.each do |object|
    size += object.size
  end
  options[:continuation_token] = object_list.next_continuation_token
  break unless object_list.next_continuation_token
end

gigabyte = (size / (2 ** 30).to_f).round(2)

CLIだとさらに簡単
AWS CLIを使ってS3上にあるファイル数とファイルサイズの合計を取得する

$ aws s3 ls s3://[バケット名]/[フォルダ名]/  --recursive --human --sum

実装後にLambdaに書けばよかったなーーと反省。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Quarkusを動かしてdocker imageをAmazonECSで動かすまで

別サイト

に書いていたのだけどサ終してしまったので移行した記事その5
2019/03/16のもの

Quarkus

Javaのフレームワーク。
特徴としてはネイティブ用のバイナリを生成して起動がめっちゃ早いとかそんな

前提条件

  • Java 8以上
  • Maven 3.5.3以上

らしい

GraalVM

GraalVMをインストール
.bash_profile等に設定

export GRAALVM_HOME=[インストールフォルダ]/Contents/Home

新規プロジェクト

Quarkusの公式に従いプロジェクト作成コマンド実行

mvn io.quarkus:quarkus-maven-plugin:0.11.0:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=getting-started \
-DclassName="org.acme.quickstart.GreetingResource" \
-Dpath="/hello"

実装

公式スタートガイドに従って作成
ソースはデフォルトでも入っているのでそのままでも動きます
公式ではAPIの追加とかこうやるんだぞみたいなのが書いてあるのでそのままやる
動かすときのコマンドを理解するのが大事ぽさ

ローカル起動

mvn compile quarkus:dev

jar作成

mvn package

jar起動

java -jar target/getting-started-1.0-SNAPSHOT-runner.jar

ネイティブバイナリ作成

mvn package -Pnative

ネイティブバイナリ起動

./target/quarkus-quickstart-runner

コンテナ作成

mvn package -Pnative -Dnative-image.docker-build=true

dockerビルド

docker build -f src/main/docker/Dockerfile -t quarkus .

run

docker run -i --rm -p 8080:8080 quarkus

ローカルでもjarでもバイナリでもdockerでも同じように動きました

$ curl http://localhost:8080/hello/greeting/quarkus
hello quarkus

AWS側へのデプロイ

dockerイメージを作成できたのでAWS ECSへのデプロイまでをやる

ECR作成

新規リポジトリの作成で「quarkus」という名前で作ってみる

イメージのpush

作成したリポジトリの「プッシュコマンドの表示」に従ってコマンド実行

aws ecr get-login --no-include-email --region ap-northeast-1

## ログインコマンドが出るのでそのまま実行

## 作成したイメージをタグ付け
docker tag quarkus:latest xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/quarkus:latest

## push
docker push xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/quarkus:latest

ECR上でpushされていれば成功

タスク作成

ECSからタスク定義作成
イメージのところにECRで定義されているものを入力して作成
DockerfileのEXPOSEをいじればいいのかもしれないがデフォルトが8080で受けるようなので
ポートマッピングは80 8080で設定する

570dbdee79d13c11f76eb76746411c12.jpg

462c242304492b7be7726ee289ea0451.jpg

4b566c405019460beb9fc6ea122a3293.jpg

クラスター作成

クラスターの作成を選択
EC2付きにしてインスタンスタイプやインスタンス数等を自由に選んで作成
EC2がアクティブになるまでまつ
a9d948c74ad61c6ba18018c1f59b8730.jpg

タスク実行

タスクのタブからタスクの実行を選択して実行
10208d5eaa7026ca42782fa2915af10d.jpg

少し待つとRUNNINGになる
670d7cf11665e5b5b8a996b5d126f51c.jpg

sshでインスタンスに入って確認
80で受けて8080にマッピングされるようになってる

$ docker container ls
CONTAINER ID        IMAGE                                                       COMMAND                  CREATED             STATUS              PORTS                  NAMES
29c1fb97dc07        xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/quarkus   "./application -Dqua…"   9 seconds ago       Up 8 seconds        0.0.0.0:80->8080/tcp   ecs-quarkus-5-quarkus-e491bef2eefec1fcfc01
0187c98c619b        amazon/amazon-ecs-agent:latest                              "/agent"                 About an hour ago   Up About an hour                           ecs-agent

接続確認

ブラウザから見ると

http://[EC2のパブリックDNS]/hello/greeting/quarkus

69be9989a2ab0cf1959a48784034d466.jpg

表示された!

感想

Quarkusの起動は、あれ、もう起動してるのかという感覚になるぐらい早かった
普段Javaを使うことが多いのだけど起動が長いのはお約束だったのでかなり新鮮
ECSは作成に最初何度か手間取ったけどなんとかできてよかった
クラスターとかタスクというのがなんなのかいまいちとっつきづらかった。。。
あとはポートマッピングのところで最初は動的マッピングとかにしてたらポートがずれてて動かないってのではまったり。。。
更新するときは

  • Javaを書く
  • dockerイメージ作成
  • ECRにpush
  • ECSにタスク定義(リビジョン作成)
  • サービス更新でリビジョンを更新

という感じなのかな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS][docker] docker build 時にインターネット接続に失敗

AWS EC2 上で docker イメージをビルドしようとして、ハマったので覚え書き。

現象

docker イメージのビルドで以下のエラーになった。
内容は fluentd のイメージにプラグインの gem を追加するというもの。

$ docker build . --no-cache
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM fluent/fluentd:v1.11.4-2.0
v1.11.4-2.0: Pulling from fluent/fluentd
df20fa9351a1: Pull complete
5bbb4150a5a0: Pull complete
0a2f393cb307: Pull complete
b1dea35b5d99: Pull complete
65de893ed157: Pull complete
Digest: sha256:0c6ae6c72902cbc3a4f60bc15069cb89fd72b57391bff150960a6f911361d6c9
Status: Downloaded newer image for fluent/fluentd:v1.11.4-2.0
 ---> 7bcbcb847e0d
Step 2/4 : USER root
 ---> Running in 79fb5c74b17f
Removing intermediate container 79fb5c74b17f
 ---> 03af9cdb324e
Step 3/4 : RUN gem install         fluent-plugin-cloudwatch-logs
 ---> Running in 1bcf43a8efb2
ERROR:  Could not find a valid gem 'fluent-plugin-cloudwatch-logs' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - no such name (https://rubygems.org/specs.4.8.gz)
ERROR: Service 'fluentd' failed to build : The command '/bin/sh -c gem install         fluent-plugin-cloudwatch-logs' returned a non-zero code: 2

rubygems.org に接続できないというもので、調べてみるとプロキシの設定が必要などの情報が出てくる。
が、ホスト上では接続できているので関係はなさそう。

サイトへの直接アクセスもできず、名前解決もできていない。
で、分かったのはネットワークインターフェースの eth0 がないこと。

$ docker build . --no-cache
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM fluent/fluentd:v1.11.4-2.0
 ---> 7bcbcb847e0d
Step 2/3 : USER root
 ---> Running in c80c0a20cd21
Removing intermediate container c80c0a20cd21
 ---> 16f2ba30695a
Step 3/3 : RUN ifconfig
 ---> Running in 1d9cb7d5e2ec
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Removing intermediate container 1d9cb7d5e2ec
 ---> de98e34f64e7
Successfully built de98e34f64e7

ホスト側で見てみると docker0 がなく、docker network でデフォルトの bridge がいないことが分かった。

$ docker network ls
NETWORK ID          NAME                           DRIVER              SCOPE
2d9ce67d06f5        host                           host                local
9a455bcae53c        none                           null                local

確認

ホストの OS は Amazon Linux 2 (EKS-Optimized) ですが、別のサーバでは問題なくビルドできていたので、比べてみるとバージョンが違っている。

  • amazon-eks-node-1.11-v20190220 → ビルドできている環境
  • amazon-eks-node-1.17-v20201002 → ビルド失敗する環境

それぞれの docker のバージョンを見てみると、以下の違いがあった。

# ビルドできている環境
$ docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a215d7133c34aa18e3b72b4a21fd0c6136
 Built:             Mon Jan 28 21:03:38 2019
 OS/Arch:           linux/amd64
 Experimental:      false
    :

# ビルド失敗する環境
$ docker version
Client:
 Version:           19.03.6-ce
 API version:       1.40
 Go version:        go1.13.4
 Git commit:        369ce74
 Built:             Fri May 29 04:01:26 2020
 OS/Arch:           linux/amd64
 Experimental:      false
    :

なお、ビルドできている方ではデフォルトの bridge ネットワークが存在している。

$ docker network ls
NETWORK ID          NAME                               DRIVER              SCOPE
2f3fd9d451c6        bridge                             bridge              local
cd2fe4f72dff        host                               host                local
f56d76b46acb        none                               null                local

この bridge の有無が docker build のエラー原因かは情報を見つけられなかったのですが、デフォルト bridge の有無の方向で調べてみると /etc/docker/daemon.json という設定ファイルが出てきたので上記のサーバで比較してみた。

ビルドできている環境

/etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "10"
  },
  "live-restore": true,
  "max-concurrent-downloads": 10,
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Soft": 2048,
      "Hard": 8192
    }
  }
}

ビルド失敗する環境

/etc/docker/daemon.json
{
  "bridge": "none",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "10"
  },
  "live-restore": true,
  "max-concurrent-downloads": 10
}

bridge: none という設定が関係しそう。

対処

/etc/docker/daemon.json ファイルの bridge: none の設定を削除して docker サービスを再起動してみる。

$ sudo service docker restart

上記のコマンドでサービス再起動をしてもすぐ解決しなかった。
その後、stop、start と別にしたり、systemctl コマンドで操作したりしてるうちにデフォルト bridge ができた。

どうも、稼働中のコンテナがいたり、docker-compose で作られるネットワークがいるとダメなようで、すべてのコンテナを停止して、後から作られたネットワークも削除してから docker サービスを再起動したら設定が反映された。

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c4433dcfbe9b        bridge              bridge              local
2d9ce67d06f5        host                host                local
9a455bcae53c        none                null                local

先ほどの docker build を試してみると、eth0 もできてる。

$ docker build . --no-cached
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM fluent/fluentd:v1.11.4-2.0
 ---> 7bcbcb847e0d
Step 2/3 : USER root
 ---> Using cache
 ---> 15b06f798b72
Step 3/3 : RUN ifconfig
 ---> Running in 713f15663040
eth0      Link encap:Ethernet  HWaddr 02:42:AC:12:00:02
          inet addr:172.18.0.2  Bcast:172.18.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:2 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:180 (180.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Removing intermediate container 713f15663040
 ---> 6d99f256433b
Successfully built 6d99f256433b

元の fluentd のビルドを試してみると、gem のダウンロードもできて、問題なくビルドができるようになった。

$ docker build . --no-cached
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM fluent/fluentd:v1.11.4-2.0
 ---> 7bcbcb847e0d
Step 2/4 : USER root
 ---> Using cache
 ---> a733f7c02648
Step 3/4 : RUN gem install         fluent-plugin-cloudwatch-logs
 ---> Running in 1d43a2226fb4
Successfully installed jmespath-1.4.0
Successfully installed aws-partitions-1.391.0
Successfully installed aws-eventstream-1.1.0
Successfully installed aws-sigv4-1.2.2
Successfully installed aws-sdk-core-3.109.2
Successfully installed aws-sdk-cloudwatchlogs-1.38.0
Successfully installed fluent-plugin-cloudwatch-logs-0.11.1
7 gems installed
Removing intermediate container 1d43a2226fb4
 ---> f7242c21bd02
Step 4/4 : USER fluent
 ---> Running in 7baaad7e3b2d
Removing intermediate container 7baaad7e3b2d
 ---> c39b2ddf48d9
Successfully built c39b2ddf48d9

ビルドエラーは解消できたのでひとまずは完了ですが、なぜ新しいバージョンの docker、OS 環境で bridge が無効化されているのかが分からない。

デフォルトの bridge は作るべきではないという情報も見かけたのだけど、なぜダメで、bridge を作らないのであればどうすべきかというはっきりとした情報が見つけられなかった。

あるいは EKS-Optimized なイメージなので、通常はランタイムのみの利用で docker build の実行を想定していないのかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SAM が自動生成するリソースの調べ方

tl;dr

AWS でサーバレス開発するときによく使う SAM (Serverless Application Model)、いろいろリソースを自動的に作ってくれて便利なんですが、ときどき自動で作成されるリソースの論理 ID がドキュメントされてなくて、自動作成されたリソースが参照できなくて困るときがあります。

そんなときは、 sam deploy の出力にある LogicalResourceId を見れば論理 ID がわかります。ここに出てるの LogicalResourceId を指定することで、 template.yaml からそのリソースを参照することができます。

image.png

この例の場合 AWS::ApiGateway::RestApi が自動で作られたリソースなんですが、その ID が "ServerlessRestApi" というのがわかる、という寸法です。

LogicalResourceId は、 sam deploy の出力に加えて、 AWS マネジメントコンソールからも見ることができます。

細かい説明

AWS SAM?

AWS が提供している Serverless Application Model (SAM) は、サーバレス開発で使うリソースのデプロイを宣言的に書くための仕組みです。 AWS で Infrastruture as Code を実現する仕組みである CloudFormation の拡張として実装されています。SAM を使うと、簡単な定義で Lambda や APIGateway など、必要なリソースをいろいろと立ち上げてくれて、けっこう便利です。

自動生成リソースと、まれにある困ったこと

この自動で作られるリソースをテンプレート内で参照したい場合、基本的にはドキュメントにかかれているので、その値を使えばよいのですが、たまーにリソース名がドキュメントに書かれていないものがあります。

たとえば下記のような template.yaml を用意して、sam-cli を実行することで、Lambda 関数に加えて、API Gateway の RestApi や、実行に必要なロールなど諸々のリソースが一発で作ってくれ、 https://......./api_endpoint という Web API が立ち上がります。

template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: CloufFormation Template for project_name

Resources:
  EndPointFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.8
      CodeUri: src
      Handler: lambda_handler.lambda_handler
      Events:
        API:
          Type: Api
          Properties:
            Path: /api_endpoint
            Method: post

このとき、実際に生成された API のフルパスを出力しようとすると、API Gateway の RestApi リソースを参照する必要があるのですが、SAM のドキュメント には a default AWS::Serverless::Api resource is created とあるのみで、テンプレート内から参照するための論理 ID は書かれていません(2020/11 現在)

これまで、どうやって自動で作られるリソースを参照したいときにどうすればいいのか途方に暮れてしまい、手動でリソースを作るようにしていたのですが、、、

解決策

ある日、LogicalResourceId をみればよいことに気づきました。

ここの値を使うことで、例えば

template.yaml
Outputs:
  ApiEndpointURL:
    Description: API Endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/${ServerlessRestApi.Stage}/api_endpoint"

といったように、 ServerlessRestApi を参照して、必要な情報を出すことができます!

余録

まあ、こういうドキュメントをみても論理 ID がわからないものがあれば、ドキュメントに PR を投げる、というのが本筋ではありますが、どう直せばよいかを知るのと、当座の作業を進めるための方法として、残しておきます。

StackOverflow でも困ってる人がいたので、回答しておきました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Redshift でcliから複数のクエリを投げてみた

Amazon Redshiftで複数クエリを投げる時にめちゃくちゃ苦労したので、まとめてみる。
利用環境はMacです。
前回の記事に引き続いて検証しました〜!

【背景】

  • Athenaで複数のクエリを投げてみました(こちらご参照ください)が、Redshiftも同じ感じで出来そう?
  • 出来るなら検証しちゃった方がいいしやってみよう!!

こんな課題感で手を動かし始めました。

【実際にやったこと/考えていたこと】

  • cliからクエリ投げられる方法を探す
    • 全然見つからない?
  • rubyのgemを使ってみる
    • 何使ったらいけるのかわかりにくい!!
    • 英語のドキュメントでわかりにくい!!

結局二つ目のgemで複数クエリ投げることができましたので、詰まった一つ目と一緒にまとめていきたいと思います〜!
(会社の先輩にとても助けていただきました?‍♂️)

cliからクエリを投げられる方法を探す

公式ドキュメントを参考にcliからawsに接続する方法を色々と探していました。
クラスターの削除や、追加については書いてあるものの、DBを立てて、クエリを投げるとこまで書いてありませんでした。

JSONでスクリプト書いてcurlで送れるだろ〜。どっかにサンプルコード書いてるだろ〜と思っていたので、なかなか調べても出てこなく、どんどん沼にはまっていった気がします。。。

ここで、先輩に一度相談して、"Gemでいいのあると思うから調べてみて〜"とのこと。
全く、考えになかったですが、"Gem redshift"と検索すると何件がヒットしました。そこで"aws-sdk-redshift"というのを見つけました。

rubyのgemを使ってみる

こちらの記事を参考にやってみてました〜!

まずaws-sdkをインストールする

日本語の参考ドキュメントの通り進めていくと(少し違いますが)、まず以下のスクリプトでレスポンスが返ってきました。
ここまででエラーが出たら、クラスターが作成できているか、必要なIAMがアタッチされているかどうかなどこちらの記事を参考にデバックしてみてください。

Redshiftとの接続
require 'aws-sdk'

cluster_identifier = "クラスター識別子"

aws_client = AWS::Redshift.client.new(
        :redshift_endpoint => "エンドポイント",
        :access_key_id => 'xxxxxxxxxxxx',
        :secret_access_key => 'xxxxxxxxxxxxxxx'
)

cluster = aws_client.describe_clusters(
                :cluster_identifier => cluster_identifier
).clusters[0]


dbuser=cluster.master_username
dburl="DBI:Pg:dbname=#{cluster.db_name};host=#{cluster.endpoint.address};port=#{cluster.endpoint.port}"

puts dbuser
puts dburl

よし!じゃあこのまま、クエリを投げよう!!!!(......どうやって???)
となりました。

あまり意味もわからないまま、認証はできた。
あまり意味もわからないが、DBも接続できた??
あまり意味もわからないから、クエリを投げるメソッドがあるのかわからない。。。

となっていました。

aws_client.methods

で、しらみつぶしにメソッド名を見て行ってもそれらしきメソッドもなく、(api_requestsがそれか!!と思いましたが、うまくできませんでした。)

ここでもう一度先輩に聞きに行くことに、、、、
すると"RedshiftDataAPIServiceってのあるけどこれは??"とのこと、、、
なんで気がつかなかったんだ!!

RedshiftDataAPIService見たら、execute_statementとかget_statement_resultある

そこからは早かったです。
先ほどのスクリプトを以下のように書き換えました。

クラスターのdbに対してクエリを投げる
require 'aws-sdk'

aws_client =  Aws::RedshiftDataAPIService::Client.new(
        access_key_id: '********************',
        secret_access_key: '************************',
      )
10.times do |i|
  aws_client.execute_statement({
    cluster_identifier: 'クラスター識別子',
    database: 'DB名',
    db_user: 'マスターユーザー名',
    sql: 'select * from db.table名'
   })
end

すると、、、何の音沙汰もない、、、、
そりゃそうです。これはクエリを投げるためのメソッド。
management consoleに見に行ったら、クエリが実行されてました。

ちなみに、レスポンスも返ってきてます。↓公式ドキュメント参照
image.png

ここで返ってきてるidを取得して、get_statement_resultでクエリの結果も確認することができます〜!!
※レスポンスをparseして、表示するところまで一つのスクリプトで書きたかったのですが、ここはまだ出来ていません。。。
今後やっていきたいと思います!!

まとめ

  • athenaでやった通り進めたらいけると思っていたら、全然方向性が違ったので、大変だった
  • 詰まったときに何で詰まっているのか、何のためにしているのかを明確にすると、方向転換ができる

英語のドキュメントが多く、日本語が少なくて大変だったので、まとめます
引き続き学びがあれば更新していきたいと思います〜:)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS SAM] テンプレートの階層化(スタックのネスト)

AWS::Serverless::Applicationを使用したテンプレートの階層化

サービスごとにテンプレートファイルを分けて以下のような構成を作る

src/
  ├─ api-gateway/
  │   ├─ template.yaml  # API Gateway定義、API GatewayからコールされるLambda等の定義
  │   ├─ swagger.yaml
  │   ├─ aaa/function.py # Lambda関数
  │   └─ bbb/function.py # Lambda関数
  
  ├─ iot-core/
  │   ├─ template.yaml  # IoT Coreの定義、デバイス間通信時にコールされるLambda定義等
  │   └─ ccc/function.py # Lambda関数
  
  ├─ cognito/
  │   ├─ template.yaml  # Cognitoのユーザプール定義、認証時にコールされるLambda等の定義
  │   └─ ddd/function.py # Lambda関数
  
  
  ├─ s3/
  │   ├─ template.yaml  # S3のバケット定義、バケット操作時にコールされるLambda等の定義
  │   └─ eee/function.py # Lambda関数
      
  └─ template.yaml  # 各サービスのtemplate.yamlをまとめるためのtemplate.yaml

各サービスフォルダ以下のtemplate.yamlapi-gateway/template.yaml等)は、通常通りCloudFormationまたはSAMの書式に従って記述する

src/template.yamlで、各サービスフォルダ以下のtemplate.yamlで定義されたリソースをアプリケーション(AWS::Serverless::Application)として定義する

AWS::Serverless::Applicationの構文については公式ドキュメント参照

src/template.yamlは以下のようになる

src/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:

  CognitoStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: cognito/template.yaml # template.yamlのパス
      Parameters:
        StackName: !Ref AWS::StackName

  ApiGatewayStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: api-gateway/template.yaml
      Parameters:
        StackName: !Ref AWS::StackName
        S3BucketName: !GetAtt S3Stack.Outputs.S3BucketName

  IoTCoreStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: iot-core/template.yaml
      Parameters:
        StackName: !Ref AWS::StackName

  S3Stack:
    Type: AWS::Serverless::Application
    Properties:
      Location: s3/template.yaml
      Parameters:
        StackName: !Ref AWS::StackName

Locationに各template.yamlのパスを指定、
Parametersに各template.yamlに渡すパラメータを指定している

Parametersで渡された値は以下のように参照する

Parameters:
  StackName:
    Type: String

Resources:
  testFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub '${StackName}_testFunction'
      ...

テンプレート間のパラメータ受け渡し

例えばs3/template.yamlで生成したバケット名をapi-gateway/template.yamlで参照したい場合、
s3/template.yamlは以下のようにOutputsを使用してバケット名を出力する

s3/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  StackName:
    Type: String

Resources:
  InfoS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${StackName}-test-bucket'
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

Outputs:
  S3BucketName:  # バケット名を出力
    Value: !Ref InfoS3Bucket  # 上記InfoS3Bucketで定義したバケットの名前を出力する

出力された値は、src/template.yamlで他のtemplate.yamlParametersで渡す

src/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  ...

  ApiGatewayStack:
    Type: AWS::Serverless::Application
    Properties:
      Location: api-gateway/template.yaml
      Parameters:
        StackName: !Ref AWS::StackName
        S3BucketName: !GetAtt S3Stack.Outputs.S3BucketName # s3/template.yamlで出力した値を渡す
  ...

  S3Stack:
    Type: AWS::Serverless::Application
    Properties:
      Location: s3/template.yaml
      Parameters:
        StackName: !Ref AWS::StackName

生成されるスタック

スタックをネストして定義すると、出力結果は以下のように、
template.yamlごとに「ネストされた」とついたスタックが生成される
1.png

親になっているスタックを削除すると、ネストされている全スタックの全リソースが削除される

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテンツ配信を日本国内に限定する方法

こんにちは!
暦の上ではもう冬になってしまいましたが、みなさんはいかがお過ごしでしょうか?
私はまだまだ秋を楽しみたいので、埼玉県にあるムーミンバレーパークで秋をたくさん感じてきました!
やっぱり、自然の中を歩くのは気持ちがいいですね。大自然とムーミンに癒されたstreampackのrisakoです:information_desk_person_tone1::fallen_leaf:

今回は、Amazon CloudFront (以下、CloudFront)を使用したコンテンツの配信制限についてです。
「CloudFrontで制限がかけられるとは!」と感動した機能でしたのでご紹介します!

構成

今回は、AWS MediaLive(以下、MediaLive)とAWS MediaStore(以下、MediaStore)を使った動画配信の構成です。もちろん、動画配信ではなくWebサイトでもCloudFrontで同様に制限をかけることができます!
今回の構成と実現したいことを図で表すとこのようになります。

  • CloudFront Distributionはすでに作成されていることを想定していますので、作成方法は省略します。
  • 動画配信構成の作成・設定方法は省略します。

スクリーンショット 2020-11-12 16.31.11.png

CloudFront設定方法

  1. 使用するCloudFront Distributionを選択
  2. 上部に表示されている項目から、Restrictions を選択
  3. 配信制限をかける前は以下のような画面になっています。今回のメインはGeo Restrictionの設定変更です!左上のEditから設定します。 スクリーンショット 2020-11-12 11.44.12.png
  4. 設定は以下のように変更します。
項目 備考
Enable Geo-Restriction Yes
Restriction Type Whitelist Blacklist は特定の国のアクセスを拒否する場合に使用します。
Countries JP -- JAPAN 複数選択可能です。

5.以上で変更は完了です!動作確認をしてみましょう。
変更後のRestrictionsの画面はこのようになっています。

スクリーンショット 2020-11-12 12.11.00.png

動作確認

日本からのアクセス

まずはじめに通常通り日本からCloudFrontにアクセスし、映像が視聴できるか確認します。
ちゃんと視聴できていますね。

スクリーンショット 2020-11-12 12.49.27.png

念の為、curlコマンドでも確認....
ちゃんと200が返って来ています!

$ curl -I https://xxxxxx.cloudfront.net/test/live.m3u8
HTTP/2 200

海外からのアクセス

次に、外国からのアクセスを想定してCloudFrontにアクセスしてみましょう。
今回は、バージニア北部に作成したEC2からアクセスしてみます。

curlコマンドで確認
403でアクセスできていないことがわかります。

$ curl I https://xxxxxx.cloudfront.net/test/live.m3u8
curl: (6) Could not resolve host: I
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
The Amazon CloudFront distribution is configured to block access from your country.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: jGIOUWRCtRzkEAA3VNpUJt6Hlyulk-lrcOs4OXbFMOcYe-DiS13tNg==
</PRE>
<ADDRESS>
</ADDRESS>

curlコマンドで確認する以外にも、webpagetestというwebサイトを使用すると、簡単に海外からのアクセスを想定して検証できます!

ちなみに、今回の場合はec2と同じバージニアからアクセスすると、このような画面が表示されるようです。
curlコマンドで表示されたものと同じですね。
しっかり、海外からのアクセスをブロックできていることがわかります!

スクリーンショット 2020-11-12 15.07.09.png

まとめ

簡単にポチポチするだけでアクセス制限がかけられるので、とても簡単でした。
一番間違えてはいけないのはwhitelistとBlacklistの箇所ですね笑
意味が真逆になってしまうので、そこだけは注意が必要だと感じました。

最後まで読んでいただきありがとうございました:wink:

参考&フリー動画素材

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/georestrictions.html
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudfront-geo-restriction/
BigBuckBunny

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パスワード認証を用いたAWS CloudWatchダッシュボード共有についての情報

1.やりたいこと

1)  共有対象者はID,パスワード認証を行ってダッシュボードを閲覧する。   
2)  特定のダッシュボードを共有する設定を行う。 

2.共有手段

CloudWatchダッシュボード共有設定には以下3通りの方法がある。

1)  特定のダッシュボードに対してユーザーに閲覧権限を与え、ID,パスワード認証を行ってもらう方法。
2)  1 つのダッシュボードを公開して共有し、リンクを持っている全員がダッシュボードを表示できるようにする方法。
3)  アカウントのすべての CloudWatch ダッシュボードを共有し、
    ダッシュボードアクセス用のサードパーティーのシングルサインオン (SSO) プロバイダーを指定する方法。

やりたいことは1)なのでパスワード認証について考えてみる。

3.CloudWatchダッシュボード共有概要図

おそらく以下のような感じ
image.png
        図1, 1)の手段を用いたCloudWatchダッシュボード共有概要図

4.作成されるリソース

CloudWatch ダッシュボード共有設定において、下記項目分のリソースが追加される。

1)  Cognitoユーザープール    
2)  Cognitoユーザー 
3)  Cognitoアプリクライアント  
4)  Cognito IDプール 
5)  IAMロール    
6)  マイクロサイト   

※Amazon Cognito リソースに関しては米国東部(バージニア北部)にて作成される。
※マイクロサイトはダッシュボード共有に際してホスト用として自動的に作られる。
 マイクロサイトに関しては自分のAWSアカウントに対して作られるものではないそうで、こちらでは管理できない。

5.アカウント関連

ユーザーの追加、削除、グループの設定はAmazon Cognitoで行う。

以下、共有に際しての役割について概要を示す。

1,  ユーザー            
        ここでは共有対象者を指す。ユーザー作成はダッシュボード共有設定初期段階か、     
        Cognitoユーザープールより追加できる。後者の場合、共有するには共有用グループに追加することによって共有できる。        
2,  グループ            
        CloudWatch ダッシュボード共有設定の過程で作成されたCognitoユーザープール内のグループは、IAMロールが割り当てられている。        
        このグループにCognito内のユーザーを追加することによってダッシュボードを閲覧することができるようになる。     
3,  IAMロール            
        IAMロールにはCloudWatchの特定のダッシュボードへのアクセス権限が割り当てられている。          

image.png

6.料金

CloudWatchダッシュボードにより参照される際の通常の API コール料金に加えて、Cognitoの料金がかかる。

CloudWatchダッシュボード                                                                                                                                                     
    ユーザーが欲しい情報によってバリエーションが多いため省略。                                                                                                                                                 

Amazon Cognito                                                                                                                                                      
    Cognitoは月間アクティブユーザー(MAU)にのみ基づいて計算される。                                                                                                                                                 
    その月にサインアップ、サインイン、トークンの更新またはパスワードの変更など、当該ユーザーに関わる ID 操作が発生した場合、                                                                                                                                                  
    そのユーザーは MAU としてカウントされる。その月に非アクティブなユーザー分は課金されない。

マイクロサイト
    マイクロサイトに関しては課金はなし。                                                                                                                                  

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSのsecurity groupがどのリソースと紐づけられているかを見つける方法。

背景

気づいたら増えまくってるsecurity group。
整理する際に、どれがどのリソースに使われてるのかを調べる方法。

参考 : https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-find-security-group-resources/

正確な調べ方

  1. 'セキュリティグループ'から該当sgのidを調べる。
  2. 'ネットワークインターフェース'を開き、該当idを貼り付ける。
  3. sgが紐づけられたeni一覧が出るので、そこからどのリソースについてるかを調べる。

雑な調べ方

  1. 'セキュリティグループ'から該当sgをチェックし削除を選択。
  2. 何かと紐づけられてる場合は、「消せません」とアラートが出てくるので、リンクに飛ぶ。
  3. 紐づけられてるものがない場合は、「消してもいいですが?」とアラートが出てくるので、消しても良さそうなら消す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2インスタンスにデプロイしたwebアプリケーションを、Route 53 で独自ドメインと紐づける

EC2インスタンスにデプロイしたNuxt.js アプリケーションに、 Route 53 で独自ドメインを紐づけたい。

こちらの記事を見ながら、VPC + EC2 でNuxt.js アプリケーションをデプロイしました。

しかし、EC2インスタンスに割り当てられているパブリックIPv4アドレス(例:x.xx.xxx.xx)は定期的に変更されてしまうので、せっかくなら自身で取得した独自ドメインと紐づけたいと考えました。

作業の流れ

  1. こちらの記事を参考に、アプリケーションをデプロイした。
  2. Elastic IP アドレスを、EC2インスタンスに割り当てる。
  3. Route53 で独自ドメインを取得する。 ※ 独自ドメインをお持ちの方はホストゾーンを作成する。
  4. レコードを作成し、独自ドメインを紐づける

2〜4の流れを解説します。

1. 前提条件

・2020/11/11 時点のAWSコンソール画面です。AWSコンソール画面は頻繁に変更されるので、ご注意ください。
・EC2でwebアプリをデプロイし、IPアドレスでアクセスできている。
・外部サイトで独自ドメイン を取得している。(今回は、すでに取得済みのドメインを紐付けます。)

2. Elastic IP アドレスを、EC2インスタンスに割り当てる。

・AWSコンソールで サービスEC2 を選択
・EC2ダッシュボードで、「Elastic IP」を選択
EC2-ElasticIP.png
・「Elastic IPアドレスの割り当て」をクリック
ToAssignElasticIP.png
・特に設定を変更することなく、「割り当て」をクリック
AssignElasticIP.png
・Elastic IPアドレスが発行されたことを確認し、「アクション」タブから「Elastic IP アドレスの関連付け」をクリック
スクリーンショット 2020-11-12 15.30.25.png
・作成したインスタンスとそのプライベートIPアドレスを選択して、「関連づける」をクリック
スクリーンショット 2020-11-12 15.50.20.png

以上で、EC2インスタンスにElastic IPアドレスの割り当てが完了しました。割り当てが成功されていると「関連付けられたインスタンスID」にインスタンスIDが表示されます。
スクリーンショット 2020-11-12 16.06.11.png

この段階で、http://[Elastic IPアドレス]にアクセスすると、デプロイしたアプリケーションを確認できると思います。
(Nuxt.jsアプリケーションの場合は、http://[Elastic IPアドレス]:[PORT番号]にアクセスする。
 例:http://xx.x.xxx.xx:3000

3. Route53 でホストゾーンを作成する。

今回は「ムームードメイン」で取得したドメインを利用します。
・AWSコンソールで サービスRoute53を選択し、「ホストゾーンの作成」をクリック
スクリーンショット 2020-11-12 16.28.55.png
・自分が取得したドメイン を入力し、「ホストゾーンの作成」をクリック
スクリーンショット 2020-11-12 16.38.55.png

ホストゾーンが作成されたら、登録したドメイン名をクリックしホストゾーンのレコードを確認します。
NS, SOAタイプの2種類のレコードが自動的に作成されていることを確認してください。
・レコードを作成する。
スクリーンショット 2020-11-12 16.54.28.png
「シンプルルーティング」を選択して、「次へ」をクリック→「シンプルなレコードを定義」をクリック

  1. レコード名:サブドメインにルーティングする場合は入力する。空白のままにすると、ドメイン名でルーティングする。
  2. 値/トラフィックのルーティング先:一番上の「レコードタイプに応じたIPアドレスまたは別の値」を選択し、割り当てたElastic IPアドレスを入力する。
  3. シンプルなレコードを定義」をクリック スクリーンショット 2020-11-12 17.04.07.png

以上で、http://[Elastic IPアドレス]もしくは、http://[ドメイン名]にアクセスできると思います。
(Nuxt.jsアプリケーションの場合は、http://[ドメイン名]:[PORT番号]にアクセスする。)

お疲れ様でした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CLIでEKSクラスタのEC2インスタンスを起動・停止するシェル

はじめに

ローカルPCからAWS CLIを利用して、EKSクラスタのワーカーノードであるEC2インスタンスを起動・停止します。
検証環境など、コスト削減のため、利用しない時はEC2インスタンスを停止にすると思いますが、AWS CLIで変更します。

予めjqをインストールして奥必要があります。

スクリプト

1. EKSクラスタのワーカーノードであるEC2インスタンスを起動する

start_cluster_nodes.sh
#!/bin/sh

cd `dirname $0`

CLUSTER_NAME=[クラスタ名]

AUTO_SCALING_GROUP_NAME=$(aws autoscaling describe-auto-scaling-groups --query 'AutoScalingGroups[?contains(Tags[?Key==`eks:cluster-name`].Value, `'${CLUSTER_NAME}'`)].AutoScalingGroupName' | jq -r '. | tostring' | sed -e 's/\[//' -e 's/\]//' -e 's/\"//g' )
EC2_INSTANCE_IDS=($(aws ec2 describe-instances --filter "Name=tag:eks:cluster-name,Values=${CLUSTER_NAME}" --query "Reservations[].Instances[].InstanceId" | jq -r '. | tostring' | sed -e 's/\[//' -e 's/\]//' -e 's/\"//g' -e 's/,/ /g'))

for instance_id in ${EC2_INSTANCE_IDS[@]}; do
    aws ec2 start-instances --instance-ids ${instance_id}
    aws autoscaling attach-instances --instance-ids ${instance_id} \
    --auto-scaling-group-name ${AUTO_SCALING_GROUP_NAME}
done

2. EKSクラスタのワーカーノードであるEC2インスタンスを停止する

stop_cluster_nodes.sh
#!/bin/sh

cd `dirname $0`

CLUSTER_NAME=[クラスタ名]

AUTO_SCALING_GROUP_NAME=$(aws autoscaling describe-auto-scaling-groups --query 'AutoScalingGroups[?contains(Tags[?Key==`eks:cluster-name`].Value, `'${CLUSTER_NAME}'`)].AutoScalingGroupName' | jq -r '. | tostring' | sed -e 's/\[//' -e 's/\]//' -e 's/\"//g' )
EC2_INSTANCE_IDS=($(aws ec2 describe-instances --filter "Name=tag:eks:cluster-name,Values=${CLUSTER_NAME}" --query "Reservations[].Instances[].InstanceId" | jq -r '. | tostring' | sed -e 's/\[//' -e 's/\]//' -e 's/\"//g' -e 's/,/ /g'))

for instance_id in ${EC2_INSTANCE_IDS[@]}; do
    aws autoscaling detach-instances --instance-ids ${instance_id} \
    --auto-scaling-group-name ${AUTO_SCALING_GROUP_NAME} --should-decrement-desired-capacity
    aws ec2 stop-instances --instance-ids ${instance_id}
done
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSの有料サポートプランについてまとめ

AWSのサポートについて、問い合わせした内容をまとめておきます。
いくつかのサポートの中で得た知識をまとめただけの備忘録です。

1つの契約で全ての質問に対応してもらえるか

他のアカウントの件についてはサポートを受けることはできない。(当たり前ですね笑)

サポートに加入しているアカウントについてのサポートのみのため、
複数のアカウントを契約しており、それぞれに対してサポートが必要な場合は、
それぞれのアカウントでサポート契約を結ぶ必要があります。

サポートのケース作成数に上限があるか

ケースの作成数には上限はなく、無制限のケースをオープン可能。

ケースをクローズしたのち、新たに不明点が見つかった場合には、
同じケースを再度オープンにする、または、新たなケースを作ることができます。

サードパーティ製ソフトウェアサポートについて

ビジネス以上のプランにて、サードパーティ製ソフトウェアサポートをベストエフォートで対応してもらえる。

今回は、Nginx まわりの原因調査をお願いした場合に、このように回答されました。
なお、今回は、原因が Nginx ではない(サーバの設定で解決できる)場合の可能性があったため、
その点については回答をもらうことができました。

実際に環境にアクセスして原因究明に当たってもらえるか

SSHなどでサーバにログインし、確認しながらのサポートはしてもらえない。

環境にアクセスするようなサポートが必要な場合には、
AWSサポートではなく、AWSパートナーの利用をオススメされました。
※ AWSパートナーを探したい場合には、こちらからも相談可能なようです

最低利用期間はあるか

30日間です。

最低30日契約すれば、40日目でも50日目でも、好きなタイミングでのサポートの解約が可能です。

月をまたいで30日契約した場合に料金はどのように計算されるか

料金を決定するためのAWSの月額料金は、月ごとに計算されるようです。

例)仮に、10月21日〜11月19日(30日)のみ契約していた場合

  • 10月21日〜10月31日の利用料金をもとに計算され、10月ご利用分として課金
  • 11月1日〜11月19日の利用料金をもとに計算され、11月ご利用分として課金

一度解約したあと、再度契約することは可能か

可能です。

問題が起こったときだけサポートプランを契約するような利用方法も可能とのことです。
※ ただし、毎回30日間の最低利用期間が発生します

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSWAFでブロックログのアラートをフィルタリングしつつSlackへ飛ばす

構成

  • WAFで攻撃を検知したらSlackへ飛ばす

WAF → KinesisFirehose → Lambda → Slack
       ↓
       → S3バケット

WAF

ルール設定

  • ルールは汎用的なものを設定
    • AWSManagedRulesCommonRuleSet
    • AWS-AWSManagedRulesLinuxRuleSet
  • DefaultActionはAllow

image.png

接続リソース設定

  • 紐づけたいALBを指定

image.png

Logging and metrics設定

image.png

  • KinesisiFirehoseにログ出力するようにする image.png

KinesisFirehose

Transform設定

  • kinesisFirehoseのJson出力をLambdaへ渡す
    • Lambda側でのトリガーでKinesisFirehoseを指定することはできない

image.png

Lambda設定

  • トリガー、送信先は指定なし

image.png

ソースコード

  • ランタイムはpython3.8
  • AWSManagedRulesCommonRuleSetの中でNoUserAgent_HEADERUserAgent_BadBots_HEADERがやたらでるので、Slackへは飛ばしたくない
  • それ以外のルールはSlackへ飛ばす
import base64
import json
import os
import urllib.request

def lambda_handler(event, context):
    output = []

    for record in event['records']:
        output_record = {
            'recordId': record['recordId'],
            'result': 'Ok',
            'data': record['data']
        }
        output.append(output_record)

        a = base64.b64decode(record['data'])
        b = json.loads(a)
        # if b['action'] != 'ALLOW':
        #     print("action not ALLOW")
        if b["ruleGroupList"][0]["terminatingRule"]["ruleId"] != 'NoUserAgent_HEADER' and b["ruleGroupList"][0]["terminatingRule"]["ruleId"] != 'UserAgent_BadBots_HEADER':
            print("terminatingRule is not NoUserAgent_HEADER")
            response = post_slack(b)
            print(output_record)

    return {'records': output}

def post_slack(msg):
    send_data = {
        "username": "notify_slack",
        "icon_emoji": ":vampire:",
        "color":"#D00000",
        "text": "WAF攻撃検知",
        "attachments":[
            {
                "fallback":"fallback Test",
                "color":"#D00000",
                "fields":[
                    {
                        "title":"詳細内容",
                        "value":str(msg)
                    }
                ]
            }
        ]
    }


    send_text = "payload=" + json.dumps(send_data)
    # send_text = "payload=" + json.dumps(jsondata)

    method = 'POST'
    headers = {'Content-Type': 'application/json'}
    WEB_HOOK_URL = os.environ['WEBHOOK_URL']

    request = urllib.request.Request(
        WEB_HOOK_URL,
        data=send_text.encode('utf-8'),
        method=method
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode('utf-8')
        print("send ok")

環境変数

  • SlackのWebhookURLを指定する

image.png

動作確認用のテストデータ

  • kinesisFirehoseから送られるであろうデータ形式
  • dataはkinesisFirehoseからs3バケットに出力されているWAFのログをbase64エンコードしたもの
  • lambda関数では、data部分をデコードし、terminatingRuleを判定するようにしている
{
  "invocationId": "invocationIdExample",
  "deliverySteamArn": "arn:aws:kinesis:EXAMPLE",
  "region": "ap-northeast-1",
  "records": [
    {
      "recordId": "49546986683135544286507457936321625675700192471156785154",
      "approximateArrivalTimestamp": 1495072949453,
      "kinesisRecordMetadata": {
        "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
        "subsequenceNumber": "123456",
        "partitionKey": "partitionKey-03",
        "shardId": "shardId-000000000000",
        "approximateArrivalTimestamp": 1495072949453
      },
      "data": "ew0KCSJ0aW1lc3RhbXAiOiAxNjA0Nzc0MzM1MTEyLA0KCSJmb3JtYXRWZXJzaW9uIjogMSwNCgkid2ViYWNsSWQiOiAiYXJuOmF3czp3YWZ2MjphcC1ub3J0aGVhc3QtMToxMTExMTExMTExMTpyZWdpb25hbC93ZWJhY2wvdmFtZGVtaWMtd2FmdjIveHh4eHh4eHgtY2UwYi00ZWYwLTkxZGUtYjQ0OTg1NmFmNDUxIiwNCgkidGVybWluYXRpbmdSdWxlSWQiOiAiQVdTTWFuYWdlZFJ1bGVzQ29tbW9uUnVsZVNldCIsDQoJInRlcm1pbmF0aW5nUnVsZVR5cGUiOiAiTUFOQUdFRF9SVUxFX0dST1VQIiwNCgkiYWN0aW9uIjogIkJMT0NLIiwNCgkidGVybWluYXRpbmdSdWxlTWF0Y2hEZXRhaWxzIjogW10sDQoJImh0dHBTb3VyY2VOYW1lIjogIkFMQiIsDQoJImh0dHBTb3VyY2VJZCI6ICIxMTExMTExMTExMS1hcHAvdmFtZGVtaWMtZGV2LWJ1c2luZXNzLWRldjItYWxiLzI4YWU3NjcyYzExMTExMWRjIiwNCgkicnVsZUdyb3VwTGlzdCI6IFsNCgkJew0KCQkJInJ1bGVHcm91cElkIjogIkFXUyNBV1NNYW5hZ2VkUnVsZXNDb21tb25SdWxlU2V0IiwNCgkJCSJ0ZXJtaW5hdGluZ1J1bGUiOiB7DQoJCQkJInJ1bGVJZCI6ICJOb1VzZXJBZ2VudF9IRUFERVIiLA0KCQkJCSJhY3Rpb24iOiAiQkxPQ0siLA0KCQkJCSJydWxlTWF0Y2hEZXRhaWxzIjogbnVsbA0KCQkJfSwNCgkJCSJub25UZXJtaW5hdGluZ01hdGNoaW5nUnVsZXMiOiBbXSwNCgkJCSJleGNsdWRlZFJ1bGVzIjogbnVsbA0KCQl9DQoJXSwNCgkicmF0ZUJhc2VkUnVsZUxpc3QiOiBbXSwNCgkibm9uVGVybWluYXRpbmdNYXRjaGluZ1J1bGVzIjogW10sDQoJImh0dHBSZXF1ZXN0Ijogew0KCQkiY2xpZW50SXAiOiAiMTI2LjIwOS4yMjEuNCIsDQoJCSJjb3VudHJ5IjogIkpQIiwNCgkJImhlYWRlcnMiOiBbDQoJCQl7DQoJCQkJIm5hbWUiOiAiSG9zdCIsDQoJCQkJInZhbHVlIjogImRldjIudmFtZGVtaWMuanAiDQoJCQl9LA0KCQkJew0KCQkJCSJuYW1lIjogIkFjY2VwdCIsDQoJCQkJInZhbHVlIjogIiovKiINCgkJCX0NCgkJXSwNCgkJInVyaSI6ICIvIiwNCgkJImFyZ3MiOiAiIiwNCgkJImh0dHBWZXJzaW9uIjogIkhUVFAvMS4xIiwNCgkJImh0dHBNZXRob2QiOiAiR0VUIiwNCgkJInJlcXVlc3RJZCI6ICIxLTVmYTZlOWJmLTIyMmU3NmNkNWJkNDQyYTEyYjg5YmM5YSINCgl9DQp9"
    }
  ]
}

テスト

参考

https://dev.classmethod.jp/articles/aws-waf-block-log-pipeline/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MacOS】aws-sam-cliをインストールする。

前提

  • MacOS
  • pipコマンドを利用できる。
  • brewコマンドを利用できる。

aws-sam-cliをインストールする。

pip install aws-sam-cli
brew tap aws/tap
brew install aws-sam-cli

aws-sam-cliをUpgradeする。

brew upgrade aws/tap/aws-sam-cli

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS CloudFormation][AWS SAM] Cognito ユーザプール、アプリクライアント定義

ユーザプール定義

構文については公式ドキュメント参照

設定したい項目以外はデフォルトで定義したものが以下

  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub '${StackName}_UserPool'
      UsernameConfiguration: 
        CaseSensitive: false
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: true
      AccountRecoverySetting:
        RecoveryMechanisms:
          - Name: admin_only
            Priority: 1
      LambdaConfig:
        PreAuthentication: !GetAtt PreAuthenticationHook.Arn

マネージメントコンソールで内容確認

属性

3.png

  • サインインに使用する属性はユーザ名

Emailや、電話番号を使用する場合は、AutoVerifiedAttributesで指定する

  • 追加属性はなし

追加したい場合は、AliasAttributesで指定する

  • ユーザ名入力で大文字、小文字を区別しない

テンプレートの以下部分で設定

        UsernameConfiguration:
          CaseSensitive: false

ポリシー

4.png

  • 管理者のみサインアップ可能とする

テンプレートの以下部分で設定

      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: true

MFAそして確認

5.png

  • アカウント回復方法をなしとする

テンプレートの以下部分で設定

        AccountRecoverySetting:
          RecoveryMechanisms:
            - Name: admin_only
              Priority: 1

トリガ

9.png

  • 認証前にフックするLambdaを指定

テンプレートの以下部分で設定

        LambdaConfig:
          PreAuthentication: !GetAtt PreAuthenticationHook.Arn

上記以外のタイミングでフックするLambda設定に関しては公式ドキュメント参照

以下、Lambdaの定義例
関数本体と、Cognitoに与えるLambda実行権限を定義

    PreAuthenticationHook:
      Type: AWS::Serverless::Function
      Properties:
        FunctionName: !Sub '${StackName}_PreAuthenticationHook'
        CodeUri: pre-Authentication-hook/
        Handler: function.lambda_handler
        Runtime: python3.8

    LambdaAddPermission:
        Type: AWS::Lambda::Permission
        Properties:
          Action: lambda:InvokeFunction
          FunctionName: !GetAtt PreAuthenticationHook.Arn 
          Principal: cognito-idp.amazonaws.com

アプリクライアント定義

構文については公式ドキュメント参照

設定したい項目以外はデフォルトで定義したものが以下

  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref UserPool # 定義したユーザプールを参照
      ClientName: !Sub '${StackName}_clientApp'
      GenerateSecret: false
      PreventUserExistenceErrors: ENABLED

マネージメントコンソールで内容確認

アプリクライアント

6.png

  • アプリクライアントのシークレットなし

テンプレートの以下部分で設定

  GenerateSecret: false
  • ユーザの存在エラーを防ぐを有効設定

テンプレートの以下部分で設定

  PreventUserExistenceErrors: ENABLED

トークンの有効期限設定

  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      UserPoolId: !Ref UserPool
      ClientName: !Sub '${StackName}_clientApp'
      GenerateSecret: false
      RefreshTokenValidity: 2
      AccessTokenValidity: 1
      IdTokenValidity: 1
      PreventUserExistenceErrors: ENABLED

上記のように

  • 更新トークンの有効期限:2
  • アクセストークンの有効期限:1
  • IDトークンの有効期限:1

と設定してみたところ、以下のように日に値が設定された

7.png

分単位の設定ができない。。

以下、各設定値の最大値から単位は秒と考えられるが、マネジメントコンソールで秒は設定できないのでそれもおかしい

8.png

とりあえずAWSに不具合があるようなので、有効期限は全てデフォルトで運用

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacOSでAWS CLIをupgradeする。

  • MacOS
  • AWS CLI 2
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

公式:https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-mac.html#cliv2-mac-install-cmd

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSの資格を取得してからオンプレ環境の運用保守をやった感想

こんにちは、初投稿です。

まずは自己紹介をしようと思います。
※正直どうでもいいので、すっ飛ばしてください。

新卒一年目で、IT企業に勤めてます。(インフラ整備)
大学は文系の大学でFランだったので、ほとんど大学ではゲーム(LOL、FPS)かぼーっとして生きていたため、勉強は一切してないです。

現在持っている資格は
・AWSクラウドプラクティショナー    9/24に取得
・教員免許(社会科)
ぐらいです。


本題です

さて、最近ふとした理由でAWSクラウドプラクティショナーの資格を取得しました。
理由としましては、クラウド最強!!っておもってたので、知識が少しほしかったからです。

ですが、現在就いている案件がなんとオンプレ環境の運用保守です。
いやもう、ツールの名前や製品がわからず正直泣きそうな毎日です、なんといいますか、お経聞いてるかなぁ…

ただ、ひとつ分かったことがあります。
それは…

AWS最強じゃん!!! 再認識

今仕事でやってる、運用、保守のアカウント管理だったり、ログの確認やスイッチの設定や冗長性についてもAWSマネージドサービスにお任せしちゃえば仕事楽やん!
って思いました。

※AWSマネージドサービスとは…??
https://aws.amazon.com/jp/managed-services/

今回、オンプレの環境に就いて、まぁ良くも悪くもAWSの素晴らしさを改めて認識しました。

※この記事は、AWSのがいかにぶっ壊れ性能なのかを言いたかっただけです。

またなんかあれば投稿します。

追記
1年目で取得した方がいい資格や、NWの知識についての補い方など、アドバイス欲しいです。
ちなみに、現在はLpic1とCCNA目指してます?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSを勉強する - EC2

Amazon Elastic Compute Cloud(EC2)

従量課金で利用可能な仮想サーバー。
起動、ノード追加、削除、マシンスペック変更
任意のAZにインスタンスを立ち上げてサーバーとして利用する。

EC2のセッティング

  1. 利用するAMIイメージ(OSセッティング)を選択
  2. インスタンスタイプを選択
  3. ストレージを選択
  4. セキュリティグループを選択
  5. SSHキーペアを設定

AMIイメージ

インスタンス起動用のOSイメージのこと(WindowsとかLinuxとか)。

下記の中から用途に合わせたAMIイメージを選択する

  • AWSが用意して提供しているAMI(Windowsとか)
  • 誰かが作成した有料のAMI
  • AMIを自作する

インスタンスタイプ

CPU・メモリ、ストレージ、ネットワークキャパシティなどのスペックを選択。
個々の設定でより低コストで最適なパフォーマンスを出せるセッティングが必要。
通常は、オンデマンドでインスタンスを購入する。

名前の構成↓
(ファミリー)(世代).(インスタンスの容量)
例)t2.nano

インスタンスファミリー

  • M5:汎用
  • T2:汎用
  • C5:コンピューティング最適化
  • H1:ストレージ最適化
  • D2:ストレージ最適化
  • R4:メモリ最適化
  • X1:メモリ最適化
  • F1:FPGA
  • G2:GPU

その他、詳細:https://aws.amazon.com/jp/ec2/instance-types/

リザーブドインスタンス

利用期間を長期指定して利用する形式。

スタンダード コンバーティブル
利用期間 1年(40%割引)
3年(60%割引)
1年(31%割引)
3年(54%割引)
AZ/インスタンスサイズ/
ネットワークタイプ変更可否
インスタンスファイリー/OS/
テナンシー/支払いオプションの変更可否
なし
リザーブドインスタンス
マーケットプレイスでの販売可否
可能 今後可能となる予定

ユースケース

  • 一定した状態または使用量が予測可能なワークロード
  • 災害対策などキャパシティ予約が可能なアプリケーション

スポットインスタンス

予備のコンピューティング容量をオンデマンドインスタンスに比べて割引で利用できるEC2インスタンス

  • 予備用を入札式で利用するため安い
  • 起動に通常より少し時間がかかる
  • 予備用のため途中で削除される可能性がある(一時的な拡張などの用途で利用)

Saving Plan

1~3年の期間に一定の使用量を守ることによりAmazon EC2コストを削減する。

ストレージ

基本的にはEBSを選択する。

  • インスタンスストア
    • ホストコンピュータに内蔵されたディスクでEC2と不可分のブロックレベルの物理ストレージ
    • EC2の一時的なデータが保持され、EC2の停止・終了をともにクリアされる
    • 無料
  • Elastic Block Store(EBS)
    • ネットワークで接続されたブロックレベルのストレージでEC2とは独立して管理される
    • EC2をTerminate(接続を遮断)してもEBSは保持可能で、SnapshotをS3に保持可能
    • 別途EBS料金が必要

セキュリティグループ

インスタンスへのトラフィックのアクセス可否を設定するファイアーウォール機能を提供
アクセスする範囲を設定することで安全性を高める。

キーペア

キーペアを利用して自身がダウンロードした秘密鍵とマッチした公開鍵を有するインスタンスにアクセスする。

EC2のバックアップ

EC2インスタンスは定期的にバックアップすることが重要。

  • 定期的にバックアップを取る
  • 定期的にリカバリプロセスを確認する
  • 複数のAZに重要なアプリケーションをデプロイすること
  • フェイルオーバー対応を準備すること
  • イベントをモニタリングして対応できるようにすること
  • インスタンス起動時に動的IPアドレス処理の設定を行うこと

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python (Boto3) @ Lambda で CloudWatch Logs の特定のログストリームにログを出力する

■ はじめに

Python (Boto3) @ Lambda から CloudWatch Logs の特定のログストリームにログを出力したかったので、やってみました。

■ コード

コードは、以下。

def put_logs(client, group_name, stream_name_prefix, message):
    try:
        exist_log_stream = True
        log_event = {
            'timestamp': int(time.time()) * 1000,
            'message': message
        }
        sequence_token = None
        for i in range(2):
            try:
                if exist_log_stream == False:
                    create_log_stream_response = client.create_log_stream(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix)
                    exist_log_stream = True
                if sequence_token is None:
                    put_log_events_response = client.put_log_events(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix,
                        logEvents = [log_event])
                else:
                    put_log_events_response = client.put_log_events(
                        logGroupName = group_name,
                        logStreamName = stream_name_prefix,
                        logEvents = [log_event],
                        sequenceToken = sequence_token)
            except client.exceptions.ResourceNotFoundException as e:
                exist_log_stream = False
            except client.exceptions.DataAlreadyAcceptedException as e:
                sequence_token = e.response.get('expectedSequenceToken')
            except client.exceptions.InvalidSequenceTokenException as e:
                sequence_token = e.response.get('expectedSequenceToken')
            except Exception as e:
                print(e)
                break
    except Exception as e:
        print(e)

sequenceToken の取得の仕方

こちらの記事 ( Lambda (Python) から特定のログストリームにログを書こうとして苦戦した2つのポイント - Qiita ) にもありますが、put_log_events でログを PUT する際、例外はありますが、基本的には sequenceToken を設定する必要があります。

describe_log_streams による sequenceToken の取得

記事の中では describe_log_streams によって、ログストリームの存在判定と sequenceToken の取得を行っており、describe_log_streams のレスポンスから nextToken として取得しています。

ですが、実は CloudWatch Logs のクォータ - Amazon CloudWatch Logs にも書かれていますが、describe_log_streams にはクォータによる制限があります。

  • DescribeLogStreams
    • 1 リージョン、1 アカウントあたり 5 件のトランザクション/秒 (TPS)。
      • クォータの引き上げをリクエスト可。

その為、使い方にもよりますが、出力頻度が高い場合は、クォータの制限に引っ掛かり遅延が発生することがあり、実際、Lambda から結構な頻度でログを出力したところ、describe_log_streams での遅延が発生してしまいました。

put_log_events による sequenceToken の取得

そこで、AWS サポート に相談したところ、put_log_events による sequenceToken の取得を提案されました。

put_log_events - CloudWatchLogs — Boto3 Docs 1.16.15 documentation にも記載されていますが、InvalidSequenceTokenException からexpectedSequenceToken として取得出来るみたいです。

もちろん put_log_events にもクォータの制限はありますが、describe_log_streams より緩めです。

  • PutLogEvents
    • 1 秒、1 ログストリームあたり 5 リクエスト。
      • 追加のリクエストは調整され、クォータは変更不可。
    • 1 リージョン、1 アカウントあたり 800 件のトランザクション/秒 (TPS)。
      • クォータの引き上げをリクエスト可。

今回のやり方にしたところ遅延が解消されましたので、使い方にもよりますが、出力する頻度が多い場合は put_log_events を使ったほうが良い様に思います。

逆に、今回のやり方でも遅延が発生が解消されない場合は、別のやり方を検討する必要があるかもしれません。

■ 出力先のログストリームの存在確認

上述した記事 ( Lambda (Python) から特定のログストリームにログを書こうとして苦戦した2つのポイント - Qiita ) では、describe_log_streams で、sequenceToken の取得だけではなく、ログストリームの存在確認も行っていましたが、クォータの制限により describe_log_streams は使えませんので、別のやり方で確認する必要があります。

ただ、これも put_log_eventsExceptions で確認出来るみたいです。ResourceNotFoundException に入ってきたらログストリームが存在しないということで、create_log_stream を実行します。

■ ハマりポイント

  • 上述しましたが…、最初は問題無かったのにリクエスト頻度が増えるに連れて Lambda の実行時間が伸びて非常に困りました。当然、コストにも跳ね返ってきますしね…。

  • とりあえず、AWS サポート さんに相談して良かったです。Boto3 documentation — Boto3 Docs 1.16.16 documentation に記載されているものの、相談しなかったら、見つけられなかった気がします…。どうもありがとうございました。?‍♂️?‍♂️?‍♂️

■ まとめ

参考になれば♪

???

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【簡単に変更履歴を管理】AWS Config導入事例5選【導入事例から学ぶ】

以前お伝えした運用改善系のサービスの一つAWS configで何ができるのかを調べてみました。
AWS運用改善サービスランキング【AWS Management Governance】

はじめに

AWSを運用していると
「この設定、いつ誰が何のために入れたんだろう」
という悩みを持ったことはないでしょうか?
私はインフラエンジニアを10年近く経験していますが、結構ありますね。
※細かい権限を設定されているが、謎にAdministrator権限が追加されているIAMなど。。

AWS Configはこの悩みを解決してくれる機能を持っていて、導入も簡単に行えます。
(「何のために」は推測するしかないですが、、)
それだけでなく、色々な活用方法があったのでそれを紹介していきます。

AWS Config 導入

導入手順

下記のサーバーワークスの動画を見ると分かりやすいと思います。
ハンズオン形式で設定項目をみることができます。
導入は簡単ですね。

【はじめてのAWS #09】 AWS Config を設定してみよう

AWS Configの設定

下記4つの設定項目があります。
必須の設定はリソースタイプ指定とS3です。

image.png

AWS Configの料金

従量課金のため、使ってみないとどの程度の料金になるか分かりません。

image.png

公式ページ:AWS Config の料金

Config Ruleの課金体系の理解は下記の記事を見ると分かりやすいかと思います。
[Config Rulesが激安になるのでみんな使ったほうが良いRuleを紹介します!](https://dev.classmethod.jp/articles/recommend-config-rules-for-all-user/)

AWS Config導入事例

Preziのケーススタディ(2016年)

https://aws.amazon.com/jp/solutions/case-studies/prezi/

リソース構成の変更追跡と変更管理のルールに使用した事例です。
image.png

FanDuelケーススタディ(2014年)

https://aws.amazon.com/jp/solutions/case-studies/fanduel/

AWSのマネージドサービスにAWS Configを利用した事例です。
マネージドサービスでも使えるんですね。

image.png

調べてみたらマネージドサービスでもかなり使えるようになっています。
参考:サポートされているリソースタイプ

100を超えるAWSアカウント運用におけるガードレール構築事例

https://engineering.visional.inc/blog/171/awssummit_securityguardrail/

これまでのセキュリティはゲート(チェックリストなどで厳しく管理)という考え方でした。
アジリティを維持してガバナンスコントロールを実現するために新しくガードレールという考え方が生まれています。
AWS Configを利用することでルール外の動作を自動検知する運用がガードレールになります。

image.png

隠れて守らないこともできるゲートより、ガードレールの方が理想に見えます。

セキュリティで加速するクラウドジャーニー

https://pages.awscloud.com/rs/112-TZM-766/images/C1-04.pdf

事例ではないですが、一般的なCloudConfigを使う例となります。
AWS Configは発見的コントロールに分類されたセキュリティ対策製品になります。

image.png

AWSセキュリティ導入パック(CloudShift)

https://cloudshift.stylez.co.jp/aws-security-pack/

こちらも事例ではないですが、AWSのセキュリティ対策をパッケージ化したサービスなので、
参考になると思います。
AWS Configは設定変更履歴管理とルール準拠状況確認の役割をはたしています。

image.png

さいごに

事例をみても変更履歴管理と想定外設定の発見(Config Rule)という2つの役割を果たすサービスであることが分かりました。
ルールをどうするかの検討はありますが、導入が簡単なため、まずは有効化してみるのはありかなと思います。
最終的にはガードレールの考え方を適用していきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2にdockerをインストールする

はじめに

AWS EC2にdockerをインストールする手順です。

インストール手順

  • Amazon Linux 2
ターミナル
# yumのアップデート
sudo yum update -y

# dockerのインストール
sudo amazon-linux-extras install docker -y

# dockerの起動
sudo service docker start

# ec2-userのセカンダリグループにdockerを追加する
# ※これによりsudoを利用せずとも、dockerコマンドが利用できるようになる
sudo usermod -a -G docker ec2-user

# 一度、ターミナルを再接続する

# インストールの確認
docker info
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

projen ではじめる快適 AWS CDK Construct Library 開発生活

はじめに

AWS CDK では 独自の Construct Library を作成し、npm や PyPI に公開することができます。
私も先日 cdk-ecr-image-scan-notify という Construct Library を公開したのですが
projen を使用することで Construct Library の作成~リリースがとても捗ったのでご紹介します。

本記事の内容は 以下のバージョンで動作を確認しています。

  • projen: v0.3.162
  • AWS CDK: v1.73.0

What is projen?

projen は近年複雑化しているプロジェクト構成をコードで定義し、管理するためのツールです。
AWS CDK チームのコアメンバーである Elad Ben-Israel 氏を中心に開発されています。

https://github.com/projen/projen

projen では package.json.gitignore 等、通常は自分で管理する必要のある
多くのプロジェクトファイルを自動で管理します。
ジェネレーターとしてプロジェクト作成時に各種ファイルを生成するだけでなく、
projen が継続してこれらの構成を更新し、維持します。

あらかじめ定義されているプロジェクトタイプを使用して新規プロジェクトを簡単に開始できます。
2020/11 時点で以下のプロジェクトタイプをサポートしています。

Commands:
  projen new awscdk-app-ts     AWS CDK app in TypeScript.
  projen new awscdk-construct  AWS CDK construct library project.
  projen new cdk8s-construct   CDK8s construct library project.
  projen new jsii              Multi-language jsii library project.
  projen new nextjs            Next.js project without TypeScript.
  projen new nextjs-ts         Next.js project with TypeScript.
  projen new node              Node.js project.
  projen new project           Base project.
  projen new react             React project without TypeScript.
  projen new react-ts          React project with TypeScript.
  projen new typescript        TypeScript project.
  projen new typescript-app    TypeScript app.

awscdk-construct は jsii を使用した Contruct をビルドするための環境を作成します。
jsii により TypeScript のコードから Python, Java, .NET で動作するライブラリを生成できます。

Create project

新規プロジェクトの作成

projen new awscdk-construct で Construct Library プロジェクトを作成します。

$ mkdir cdk-sample-lib && cd cdk-sample-lib

$ npx projen new awscdk-construct
npx: installed 63 in 8.37s
? Created .projenrc.js for AwsCdkConstructLibrary
? Synthesizing project...
$GIT_USER_NAME is not defined

この時点でプロジェクトディレクトリ配下に、.projenrc が作成されます。

const { AwsCdkConstructLibrary } = require('projen');

const project = new AwsCdkConstructLibrary({
  authorAddress: "user@domain.com",
  authorName: $GIT_USER_NAME,
  cdkVersion: "1.60.0",
  name: "cdk-sample-lib",
  repository: "https://github.com/user/cdk-sample-lib.git",
});

project.synth();

.projenrc の修正

使用する AWS CDK やその他のモジュールの依存関係を追加できます。

  cdkDependencies: [
    '@aws-cdk/aws-lambda',
    '@aws-cdk/core'
  ],
  deps: [ 
    'super-useful-lib' 
  ]

jsii で TypeScript 以外の言語にクロスコンパイルを行う場合はターゲット言語を追加します。

  python: {
    distName: 'cdk-sample-lib',
    module: 'cdk_sample_lib',
  },

その他に指定可能なオプションについては API.md をご確認ください。
参考例として、修正後のファイルは以下のようになります。

const { AwsCdkConstructLibrary } = require('projen');

const PROJECT_NAME = "cdk-sample-lib" 

const project = new AwsCdkConstructLibrary({
  authorAddress: "hayaok333@gmail.com",
  authorName: "hayao-k",
  cdkVersion: "1.73.0",
  name: PROJECT_NAME,
  repository: "https://github.com/hayao-k/cdk-sample-lib.git",
  defaultReleaseBranch: 'main',
  cdkDependencies: [ 
    '@aws-cdk/core',
    '@aws-cdk/aws-lambda'
  ],
  python: {
    distName: PROJECT_NAME,
    module: 'cdk_sample_lib',
  },
});

project.synth();

projen コマンドの実行

.projenrc を編集したら、projen コマンドを実行し、変更を反映します。 (yarn が必要です)

$ npx projen
npx: installed 63 in 5.491s
? Synthesizing project...
? yarn install --check-files
yarn install v1.22.5
info No lockfile found.
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
info fsevents@2.2.1: The platform "linux" is incompatible with this module.
info "fsevents@2.2.1" is an optional dependency and failed compatibility check. Excluding it from installation.
[4/5] Linking dependencies...
[5/5] Building fresh packages...
success Saved lockfile.
Done in 20.34s.
? jest: * => ^26.6.3
? @types/jest: * => ^26.0.15
? ts-jest: * => ^26.4.4
? eslint: * => ^7.13.0
? eslint-import-resolver-node: * => ^0.3.4
? eslint-import-resolver-typescript: * => ^2.3.0
? eslint-plugin-import: * => ^2.22.1
? json-schema: * => ^0.2.5
? Synthesis complete

----------------------------------------------------------------------------------------------------
Commands:

BUILD
compile          Only compile
watch            Watch & compile in the background
build            Full release build (test+compile)

TEST
test             Run tests
test:watch       Run jest in watch mode
eslint           Runs eslint against the codebase

RELEASE
compat           Perform API compatibility check against latest version
release          Bumps version & push to master
docgen           Generate API.md from .jsii manifest
bump             Commits a bump to the package version based on conventional commits
package          Create an npm tarball

MAINTAIN
projen           Synthesize project configuration from .projenrc.js
projen:upgrade   upgrades projen to the latest version

MISC
start            Shows this menu

Tips:
? The VSCode jest extension watches in the background and shows inline test results
? Install Mergify in your GitHub repository to enable automatic merges of approved PRs
? Set `autoUpgradeSecret` to enable automatic projen upgrade pull requests
? `API.md` includes the API reference for your library
? Set "compat" to "true" to enable automatic API breaking-change validation

package.json の作成とインストールをはじめ、.gitignore.npmignore、eslint、jsii の構成
ライセンスファイル等が projen により自動で生成されていることがわかります。
新規プロジェクトを作成するたびに、既存のプロジェクトからコピーしてくるといった作業が不要になります。
これらのファイルを編集する場合、必ず .projenrc を修正し、projen コマンドを再実行する必要があります。
手動で編集した場合、ビルドが失敗します。

$ tree -L 1 -a
.
├── .eslintrc.json
├── .github
├── .gitignore
├── LICENSE
├── .mergify.yml
├── node_modules
├── .npmignore
├── package.json
├── .projenrc.js
├── README.md
├── src
├── test
├── tsconfig.jest.json
├── version.json
├── .versionrc.json
└── yarn.lock

Developmet

ここでは シンプルに Hello World の Lambda を作成する例を考えてみます。
projen により以下のディレクトリがすでに作成されています。

.
├── lib/ 
├── src/
├── test/

lib ディレクトリにはコンパイルされたファイルが配置されます。
Lambda 関数のコードは CDK のコードにインラインでも挿入できますが、
今回は 別途 functions ディレクトリを作成し、配置、参照させます。

fucntions/index.js
exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

src ディレクトリには以下の2ファイルを作成します。

src/index.ts
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';

export class CdkSampleLib extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string) {
    super(scope, id);

    new Function(this, 'HelloWorld', {
      handler: 'index.handler',
      code: Code.fromAsset('functions'),
      runtime: Runtime.NODEJS_12_X,
    });
  }
}
src/integ.default.ts
import * as cdk from '@aws-cdk/core';
import { CdkSampleLib } from './index';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'MyStack');

new CdkSampleLib(stack, 'Cdk-Sample-Lib');

test ディレクトリには以下のファイルを作成しました。

test/hello.test.ts
import * as cdk from '@aws-cdk/core';
import { CdkSampleLib } from '../src/index';
import '@aws-cdk/assert/jest';

test('create app', () => {
  const app = new cdk.App();
  const stack = new cdk.Stack(app);
  new CdkSampleLib(stack, 'TestStack');
  expect(stack).toHaveResource('AWS::Lambda::Function');
});

Unit Test, Build, Release

projen から生成された package.json により、各種 scripts が定義済みです。

Unit Test

yarn test でテストを実行します。
yarn build 時も実行されるため、ここでは実行例は割愛します。

Build

yarn build でテストを実行し、TypeScript を jsii モジュールにコンパイルします。
また jsii-docgen によりコード内のコメントから API ドキュメント (API.md) を生成します。
さらに jsii-pacmak により dist ディレクトリに各言語固有の公開パッケージを作成します。

$ yarn build
yarn run v1.22.5
$ yarn run test && yarn run compile && yarn run package
$ rm -fr lib/ && jest --passWithNoTests --updateSnapshot && yarn run eslint
 PASS  test/hello.test.ts
  ✓ create app (197 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.713 s, estimated 7 s
Ran all test suites.
$ eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test
$ jsii --silence-warnings=reserved-word --no-fix-peer-dependencies && jsii-docgen
$ jsii-pacmak
Done in 51.38s.

ビルドが成功したら、以下のようにローカルでデプロイを試すことができます。

$ cdk deploy --app='./lib/integ.default.js'
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 │
├───┼───────────────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${Cdk-Sample-Lib/HelloWorld/ServiceRole.Arn}  │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴───────────────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬───────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────┐
│   │ Resource                                                  │ Managed Policy ARN                                         │
├───┼───────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤
│ + │ ${Cdk-Sample-Lib/HelloWorld/ServiceRole}                  │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLamb │
│   │                                                           │ daBasicExecutionRole                                       │
└───┴───────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘
(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
MyStack: deploying...
[0%] start: Publishing 20472c64d312cc547a9359d36b04cfc75633027c0b13c3c07b96dfdf1b1c428f:current
[100%] success: Published 20472c64d312cc547a9359d36b04cfc75633027c0b13c3c07b96dfdf1b1c428f:current
MyStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (4/4)

 ✅  MyStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/MyStack/b7e03de0-2442-11eb-b897-06ee62aadd34

削除も同様です。

$ cdk destroy --app='./lib/integ.default.js'                                        
Are you sure you want to delete: MyStack (y/n)? y
MyStack: destroying...
5:27:29 PM | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | MyStack
5:27:34 PM | DELETE_IN_PROGRESS   | AWS::IAM::Role        | Cdk-Sample-Lib/HelloWorld/ServiceRole

 ✅  MyStack: destroyed

Release

まずは変更をコミットします。

$ git add . 
$ git commit -m "feat: release 0.0.1"

yarn release でバージョンをバンプし、CANGELOG.md が自動で更新されます
その後、GitHub のリリースブランチへ push します。

$ yarn release
yarn run v1.22.5
$ yarn run --silent no-changes || (yarn run bump && git push --follow-tags origin main)
$ yarn run --silent no-changes || standard-version
✔ bumping version in version.json from 0.0.0 to 0.0.1
✔ Running lifecycle script "postbump"
ℹ - execute command: "yarn run projen && git add ."
? yarn install --check-files
? Synthesis complete

----------------------------------------------------------------------------------------------------
Commands:

BUILD
compile          Only compile
watch            Watch & compile in the background
build            Full release build (test+compile)

TEST
test             Run tests
test:watch       Run jest in watch mode
eslint           Runs eslint against the codebase

RELEASE
compat           Perform API compatibility check against latest version
release          Bumps version & push to main
docgen           Generate API.md from .jsii manifest
bump             Commits a bump to the package version based on conventional commits
package          Create an npm tarball

MAINTAIN
projen           Synthesize project configuration from .projenrc.js
projen:upgrade   upgrades projen to the latest version

MISC
start            Shows this menu

Tips:
? The VSCode jest extension watches in the background and shows inline test results
? Install Mergify in your GitHub repository to enable automatic merges of approved PRs
? Set `autoUpgradeSecret` to enable automatic projen upgrade pull requests
? `API.md` includes the API reference for your library
? Set "compat" to "true" to enable automatic API breaking-change validation

✔ created CHANGELOG.md
✔ outputting changes to CHANGELOG.md
✔ committing version.json and CHANGELOG.md and all staged files
✔ tagging release v0.0.1
ℹ Run `git push --follow-tags origin master` to publish

もしくは yarn bump を実行することで任意のバージョンにバンプすることが可能です。

$ yarn bump --release-as 1.0.0 && git push --follow-tags origin main

projen コマンド実行時に Github Actions のワークフロー定義も生成されており、
パッケージリポジトリへのリリースも簡単に自動化することができます。

  • Build workflow (.github/workflows/build.yaml):
    pull request 作成時に起動
    ライブラリのビルドおよび、改ざん (手動修正されていないか) のチェック

  • Release workflow (.github/workflows/release.yaml):
    リリースブランチへの push 時に起動
    リリースブランチでのビルドジョブ後、npm や PyPI などの各言語のリポジトリへの公開が自動で行われます。
    リポジトリへのリリースには jsii-release が使用されています。

image.png

Release ジョブを正常に動作させるには、公開する言語に応じた Secrets を
GitHub リポジトリに登録する必要があります。

  • npm: NPM_TOKEN
  • .NET: NUGET_API_KEY
  • Java: MAVEN_GPG_PRIVATE_KEY, MAVEN_GPG_PRIVATE_KEY_PASSPHRASE, MAVEN_PASSWORD, MAVEN_USERNAME, MAVEN_STAGING_PROFILE_ID
  • Python: TWINE_USERNAME, TWINE_PASSWORD

さいごに

projen を使用することで、Construct Library の実装に集中することができます。
(もちろん通常の CDK App についても)
参考の GitHub リポジトリとしては冒頭でご紹介した cdk-ecr-image-scan-notify
ご確認いただければと思います。Construct Library 自体もぜひ使ってみていただけると嬉しいです。

projen を使用した Construct Library の作成については台北の AWS Community Builder である
Neil Kuan (@guan840912) からの紹介と多くの協力により学ぶことができました。
彼の GitHub アカウントで複数の Construct Library が公開されているので、こちらもチェックしてみてください!

以上です。
参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFormationをゼロから勉強する。(その8:スタックのネスト)

はじめに

本記事でも何度か紹介しているAWS Black Belt Online Seminarですが、また新しいセミナーが公開されたようです。

3か月くらい前にもCloudFormationのセミナーが公開されていましたが、それだけCloudFormationの需要があるということなんでしょうね。


その6ではTransformを使用した他テンプレートのインクルードを試しましたが、今回はスタックをネスト構成にしてテンプレートを分割してみようと思います。

ネストとは

イメージとしてはその6Transformでのテンプレート分割と同じような方法となりますが、Transformのインクルードはベーステンプレートの一部を別ファイルにして呼び出すイメージなので、実行単位であるスタックとしては1スタックであるのに対して、ネストは他のスタックを呼び出すイメージとなるので、実行時の見え方は複数スタックとなります。

共通コンポーネント用スタックを作成して、個々のスタックから呼び出すようにするのがAWSのベストプラクティスとのことです。


・・・とはいえTransformのインクルードでも書き方次第でネストした場合と同じことができそうなのでいまいち区別がし辛いですが、ネストの場合はその6で書いたようなyamlの書き方の制約は無いので、Transformのインクルードはリソースの一部パラメータ(例えばタグ)を分割したりするのに使い、ネストはリソース自体を分割したりするのに使うといった区別が良いかと思います。

ネストの書式

ResourcesセクションにAWS::CloudFormation::Stackタイプのリソースを作成することでネストしたスタックを指定できます。

ネストしたファイル名を指定する必要があるため、PropertiesTemplateURLの指定が必須となります。

また、S3バケットへのアップロードが必要となるため、Transformでのインクルードと同様、実行する前にpackageコマンドでURLの変換、S3バケットへのアップロードを行う必要があります。

ネストしたスタックの指定方法(抜粋)
Resources:
  NestStack1:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./vpc.yaml

ネストしたスタックへの入出力

ネストのスタックは親子関係となるため、親スタックから子スタックを呼び出す構成になります。

入出力の関係は子スタックプログラミング言語の関数として考えるとイメージが湧きやすいと思いますが、親スタック子スタックからの入出力を受ける場合、子スタック(という関数)に引数を渡すのが入力で、子スタック(という関数)からの戻り値が出力となります。

子スタック側での入出力書式

これはネストしていない場合と同じくParametersセクションで上位からの入力を受けて、Outputsで結果を上位へ渡します。

親スタック側での入力書式

親スタックから子スタックへ値を入力するためにはネストするスタックのPropertiesParametersを記載して子スタックに渡す値を記載する必要があります。

上述の例で言うと、親スタックから子スタック(という関数)へ引数を渡す方法が、PropertiesParametersとなります。

子スタックへの入力値指定例
Resources:
  NestStack1:    ★子スタック名
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./vpc.yaml
      Parameters:
        NestVPCRange: !Ref VPCRange
        NestSubnetRange1: !Ref SubnetRange1

親スタック側での出力書式

子スタック(という関数)からの戻り値は!GetAtt組み込み関数で以下の書式で記載することで取得できます。

出力値(戻り値)の取得
!GetAtt [子スタック名].Outputs.[子スタック側のOutputリソース名]

例えば親スタックで前項目の子スタックへの入力値指定例の設定、子スタックで以下のような出力設定を行ったとします。

子スタック側でのOutput(抜粋)
Outputs:
  NestSubnet1Id:    ★Outputリソース名
    Value: !Ref EC2Subnet1

その場合、子スタックの出力値(戻り値)を取得するためには以下の指定を行うことで取得できます。

親スタック側でのNestStack1出力値の取得例
!GetAtt NestStack1.Outputs.NestSubnet1Id

ネストスタック間の入出力まとめ

ネストスタック間の入出力について、AWSマネジメントコンソール画面からの入出力も含めたイメージ図を描いてみました。

子スタックとのやり取り、自スタックでのやり取りとなります。

Stack.png

基本のイメージが分かれば、図のような複雑なスタック構成でも悩むことはないかと思いますが、注意点として、各スタック間の値の入出力は単純なネストの記述ではスタックを飛び越してやり取りできません。

そのため、例えば図中のNestStack2の出力値をNestStack3で使用したい場合は、まずNestStack1OutputNestStack2の戻り値をRoot Stackに渡し、次にRoot StackからNestStack3ParametersNestStack3に値を引き渡す必要があります。

もしスタックを飛び越してやり取りしたい場合は、Outputセクションで値をExportすればやり取りすることが可能なようです。
Exportする方法(Cross Stack Reference)は次回試そうと思います。

今回作成する構成

今回は以下のような構成を作ってみようと思います。

Nest.png

ルートスタック用テンプレートの作成

子スタックで使用するVPCRangeSubnetRange1SubnetRange2を読み込み、各スタックの入力に引き渡します。

また、vpc.yamlで作成するVPC IDsubnet1.yamlで作成するSubnet1のIDsubnet2.yamlec2.yamlで使用するため、vpc.yamlの出力値をsubnet2.yamlec2.yamlの入力値として!GetAttで指定するようにします。

ルートスタック用テンプレート(cf.yaml)
AWSTemplateFormatVersion: 2010-09-09
Resources:
  NestStack1:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./vpc.yaml
      Parameters:
        NestVPCRange: !Ref VPCRange
        NestSubnetRange1: !Ref SubnetRange1
  NestStack3:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./subnet2.yaml
      Parameters:
        NestVpcId: !GetAtt NestStack1.Outputs.NestVpcId
        NestSubnetRange2: !Ref SubnetRange2
  NestStack4:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./ec2.yaml
      Parameters:
        NestSubnetId: !GetAtt NestStack1.Outputs.NestSubnet1Id
Parameters:
  VPCRange:
    Type: String
    Description: "VPC Subnet Range"
  SubnetRange1:
    Type: String
    Description: "Subnet Range1"
  SubnetRange2:
    Type: String
    Description: "Subnet Range2"

VPCスタック用テンプレートの作成

VPC Rangeと、子スタックとなるsubnet1.yamlSubnet1 Rangeの情報をParametersセクションで受け取り、subnet1.yamlSubnet1 Rangeの値を渡します。

また、Subnet1のIDは、ec2.yamlでも使用するため、subnet1.yamlの出力値をvpc.yamlの出力値としてルートスタックとなるcf.yamlに渡すようにします。

VPCスタック用テンプレート(vpc.yaml)
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NestVPCRange:
    Type: String
  NestSubnetRange1:
    Type: String
Resources:
  EC2VPC1:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref NestVPCRange
      Tags:
        - Key: "Name"
          Value: "cf_VPC1"
  NestStack2:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./subnet1.yaml
      Parameters:
        NestVpcId: !Ref EC2VPC1
        NestSubnetRange1: !Ref NestSubnetRange1
Outputs:
  NestVpcId:
    Value: !Ref EC2VPC1
  NestSubnet1Id:
    Value: !GetAtt NestStack2.Outputs.NestSubnet1Id

Subnet1スタック用テンプレートの作成

親スタックとなるvpc.yamlからの値をParametersで受け、Subnet1のIDOutputsで出力するようにします。

Subnet1スタック用テンプレート(subnet1.yaml)
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NestVpcId:
    Type: 'AWS::EC2::VPC::Id'
  NestSubnetRange1:
    Type: String
Resources:
  EC2Subnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref NestVpcId
      CidrBlock: !Ref NestSubnetRange1
      Tags:
        - Key: "Name"
          Value: "cf_Subnet1"
Outputs:
  NestSubnet1Id:
    Value: !Ref EC2Subnet1

Subnet2スタック用テンプレートの作成

subnet2.yamlは他スタックで使用する値は無いので、親スタックとなるcf.yamlからの値をParametersで受けるだけになります。

Subnet2スタック用テンプレート(subnet2.yaml)
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NestVpcId:
    Type: 'AWS::EC2::VPC::Id'
  NestSubnetRange2:
    Type: String
Resources:
  EC2Subnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref NestVpcId
      CidrBlock: !Ref NestSubnetRange2
      Tags:
        - Key: "Name"
          Value: "cf_Subnet2"

EC2スタック用テンプレートの作成

subnet1.yamlで出力したSubnet1のIDParametersで読み込み、EC2インスタンスを作成します。

EC2スタック用テンプレート(ec2.yaml)
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NestSubnetId:
    Type: 'AWS::EC2::Subnet::Id'
Resources:
  EC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-0cc75a8978fbbc969
      InstanceType: t2.micro
      KeyName: staging_key
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          SubnetId: !Ref NestSubnetId
          DeviceIndex: 0

テンプレートの実行

ネスト化により、テンプレートが複数に分かれるため、その6と同様、packagedeployコマンドでテンプレートの変換と実行を行います。

URLなどのテンプレートの変換は、ルートスタックで行えば、子スタックも自動的に変換とS3への転送が行われるため、ルートスタック用テンプレートのみ実行します。

テンプレートの変換とS3バケットへの転送
aws cloudformation package --template-file cf.yaml --s3-bucket [S3バケット名] --output-template-file output.yaml

packageコマンド実行後、deployコマンドでS3バケットに転送したテンプレートを実行します。

テンプレートの実行
aws cloudformation deploy --template-file output.yaml --stack-name stack-test --parameter-overrides VPCRange=172.24.0.0/16 SubnetRange1=172.24.0.0/24 SubnetRange2=172.24.1.0/24

CloudFormationスタック画面で以下の様になれば成功です。

capture_18102020_142519.jpg

おわりに

スタックネストは親子関係となるため、構造が分かりやすく、書き方とイメージを覚えてしまえば分かりやすいと感じました。

次も同じようなテンプレート分割方法となるクロススタック参照Cross Stack Reference)を勉強してみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTTPS対応503エラー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む