20200623のAWSに関する記事は29件です。

AWS CLI で EKS クラスターの IAM OIDC ID プロバイダーを作成

$ CLUSTER_NAME=example-eks-cluster
$ ISSUER_URL=$(aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text)
$ JWKS_FQDN=$(curl -sS $ISSUER_URL/.well-known/openid-configuration | jq -r '.jwks_uri' | perl -pe 's/^https:\/\/(.+?)\/.+$/${1}/')
$ CERTIFICATE_BODY=$(openssl s_client -servername $JWKS_FQDN -showcerts -connect $JWKS_FQDN:443 < /dev/null)
$ CERTIFICATE_START=$(echo $CERTIFICATE_BODY | grep -n 'BEGIN CERTIFICATE' | sed -e 's/:.*//g' | tail -n 1)
$ CERTIFICATE_END=$(echo $CERTIFICATE_BODY | grep -n 'END CERTIFICATE' | sed -e 's/:.*//g' | tail -n 1)
$ echo $CERTIFICATE_BODY | head -$CERTIFICATE_END | tail -$(expr $CERTIFICATE_END - $CERTIFICATE_START + 1) > /tmp/certificate.crt
$ ROOT_CA_FINGERPRINT=$(openssl x509 -in /tmp/certificate.crt -fingerprint -noout | perl -pe 's/^SHA1 Fingerprint=(.+)$/${1}/' | perl -pe 's/\://g')
$ rm -f /tmp/certificate.crt 
$ aws iam create-open-id-connect-provider --url $ISSUER_URL --thumbprint-list $ROOT_CA_FINGERPRINT --client-id-list sts.amazonaws.com
{
    "OpenIDConnectProviderArn": "arn:aws:iam::999999999999:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あらためてEPELリポジトリの使い方をまとめてみた

1. はじめに

RHEL系ディストリビューションにおける、拡張パッケージのリポジトリ「EPEL」を使っている人は多いだろう。筆者もこれまでの記事で何度か紹介してきた。ところがクラウドでは状況が微妙に異なる。そこで使い方をまとめることにした。

1-1. TL;DR

  • クラウドでは、デフォルトで有効になっていることや、独自のインストールコマンドが提供されていることがある
  • 記事で毎回EPELの使い方を説明するのは無駄

1-2. 前提条件

  • RHEL系Linuxディストリビューション。FedoraやCentOS、Amazon Linux、Oracle Linuxなど

2. EPELとは

EPELを使う手順は簡単だ。すぐにインストールしたいときは「3. EPELリポジトリを有効にする」に進んでほしい。ここではEPELの概要や使用するうえでの注意事項を説明する。

2-1. もっとも有名なサードパーティー・リポジトリEPEL

EPEL(Extra Packages for Enterprise Linux)は、Fedoraプロジェクトの有志がビルドした、Red Hat Enterprise Linux (RHEL) 系Linuxディストリビューション向けオプションパッケージ群だ。LinuxのメディアやYumリポジトリに含まれないパッケージ入手先の第1候補になる。

EPELのように、ディストリビューション本家以外が提供するリポジトリを「サードパーティー・リポジトリ」と呼ぶ。EPEL以外にも、以下のリポジトリも有名である。

2-2. なぜサードパーティー・リポジトリが必要か?

理由は簡単で「使いたいアプリケーションが標準のYumリポジトリに含まれていない」もしくは「含まれていてもバージョンが古い」からだ。これはディストリビューションのサポート上の理由だ。

  • ディストリビューションベンダーは製品をサポートする責任があるので、標準リポジトリに含めるパッケージを制限している
  • 互換性の問題で、同一メジャーバージョン内で新しいバージョンを取り入れられない

これらの問題は、RHEL7までのSoftware Collections(SCL)や、RHEL8のAppStreamで改善するが、すべての問題が解決するわけではない。

2-2-1. ソースからインストールする?

これらの問題が起きたときソースからビルドする人もいるだろう。ソースコードからのインストールは否定しないが、パッケージ管理ステムのメリットが損なわれる。十分な理由のあるときだけに限ったほうがいいだろう。

ソースからビルドしたときのデメリット

  • 依存関係を保ったシステム管理が難しくなる
  • ソースRPMからビルドしたバイナリRPMはサポート対象外になる

2-2-2. 互換性の話

以下の表は、RHELバージョンごとのkernelとglibcのバージョンをまとめたものだ。同一メジャーバージョン内では、アップデートパッケージを適用してもパッケージのバージョンは変わらない。

ディストリビューション kernel glibc
RHEL6 2.6.32 2.12
RHEL7 3.10.0 2.17
RHEL8 4.18.0 2.28
Amazon Linux 2 4.14 2.26

RPMパッケージは以下のように命名される。kernelやglibcなどのコアコンポーネントの場合、yum updateを実行して変わるのはバージョン以降に付与したリリース番号である。
rpm-version.PNG
ここまでしつこく書く理由は、kernelやglibcなどのコアコンポーネントは、アプリケーションの動作保証で極めて重要だからだ。

だから出どころの分からない野良リポジトリを使ってはいけないし、RHEL6用のRPMパッケージをRHEL7にインストールするような強引なことはしてはいけない。

余談
筆者が経験したホラーストーリーがある。とある障害の支援でRHEL6の設定を確かめていたときの出来事だ。いくつかの基本コマンドが動かない。おかしいと思い、以下のコマンドでRed Hat以外のパッケージを探すと、たくさん出てきた。

VenderがRedHat以外のパッケージを表示するコマンド
rpm -qa --qf "%{name} %{vendor}\n"  | grep -v "Red Hat"

それもglibcなどのコアコンポーネントがFedoraやScientific Linuxになっている。それもリリース番号違いでなくバージョン番号違い。本来インストールできないものをnodepsforceでインストールしたようだ。そんな強引なことをしたら、正常動作しなくて当然だ。逆に動いていたことが不思議でならない。

3. EPELリポジトリを有効にする

EPELを使用するにはepel-releaseパッケージをインストールすればよい。ただし使用するクラウドサービスやLinuxディストリビューションによって以下の注意事項がある。EPELのWebサイトも見てほしい。

  • RHEL7ではoptionalリポジトリやextrasリポジトリを有効にする必要がある
  • RHEL8ではcodeready-builderリポジトリを有効にする必要がある
  • AWSのAmazon Linux 2ではEPELを有効にする専用のコマンドがある
  • Oracle Cloud InfrastructureのOracle Linux 7では、専用のEPELリポジトリがデフォルトで有効になっている

3-1. EPELを有効にする(基本)

「クラウドでRHELやCentOSを使うとき」や「オンプレミス環境」ではepel-releaseをインストールする。これが基本になる。

8系Linux OS

sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -y

7系Linux OS

sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y

6系Linux OS

sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm -y

3-2. EPELを有効にする(AWS)

Amazon Linux 2ではEPELを有効にする専用のコマンドが用意されている。AWSでもRHELやCentOSでは、前述の方法を利用する。

sudo amazon-linux-extras install epel

インストールに成功すると「amzn2extra-epel」「epel」リポジトリが追加される。

$ yum repolist enabled
repo id                    repo name                                   status
amzn2-core/2/x86_64        Amazon Linux 2 core repository                  19791
amzn2extra-docker/2/x86_64 Amazon Extras repo for docker                      28
amzn2extra-epel/2/x86_64★ Amazon Extras repo for epel                         1
epel/x86_64★              Extra Packages for Enterprise Linux 7 - x86 13141+192
repolist: 32961

amzn2extra-epelリポジトリのパッケージ数が1なのが気になる。調べてみるとepel-releaseだけが含まれていた。

repoファイルの定義を確認すると、EPELのミラーサイトを参照しており、クラウド内にAWS独自のミラーサイトがあるわけではない。ログを確認するとcloudfrontから取得している。

/etc/yum.repos.d/epel.repo
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch★ミラーサイトを取得
failovermethod=priority

3-2. EPELを有効にする(Oracle Cloud Infrastructure)

Oracle Cloud InfrastructureのOracle Linux 7では「ol7_developer_EPEL」という専用のEPELリポジトリが用意してある。そのため追加作業は不要だが、他のOSでは前述の方法を利用する。

EPELリポジトリが有効になっているか、次のコマンドで確認するといいだろう。

$ yum repolist
repo id                         repo name                                 status
ol7_UEKR5/x86_64                Latest Unbreakable Enterprise Kernel Rele   217
ol7_addons/x86_64               Oracle Linux 7Server Add ons (x86_64)       433
ol7_developer/x86_64            Oracle Linux 7Server Development Packages  1349
ol7_developer_EPEL/x86_64★これ Oracle Linux 7Server Development Packages 32336
ol7_ksplice                     Ksplice for Oracle Linux 7Server (x86_64)  7356
ol7_latest/x86_64               Oracle Linux 7Server Latest (x86_64)      18986
ol7_oci_included/x86_64         Oracle Software for OCI users on Oracle L   321
ol7_optional_latest/x86_64      Oracle Linux 7Server Optional Latest (x86 13984
ol7_software_collections/x86_64 Software Collection Library release 3.0 p 14564
repolist: 89546

repoファイルの定義を確認すると、リージョンごとに用意したOracle独自のEPELを参照している。

/etc/yum.repos.d/oracle-epel-ol7.repo
[ol7_developer_EPEL]
name=Oracle Linux $releasever Development Packages ($basearch)
baseurl=http://yum$ociregion.oracle.com/repo/OracleLinux/OL7/developer_EPEL/$basearch/

ol7_developer_EPELからインストールしたpwgenパッケージを確認すると、ビルドホスト(Build Host)やベンダ(Vendor)が「fedora」ではない。そのためEPELからソースパッケージを取得して再ビルドしていることが分かる。

$ rpm -qi pwgen
Name        : pwgen
Version     : 2.08
Release     : 1.el7
★中略
Source RPM  : pwgen-2.08-1.el7.src.rpm
Build Date  : Tue Aug 14 22:24:07 2018
Build Host  : x86-ol7-builder-01.us.oracle.com ★
Relocations : (not relocatable)
Vendor      : Oracle America ★
URL         : http://sf.net/projects/pwgen
Summary     : Automatic password generation
★以下省略

重要
「EPELからソースパッケージを取得してビルド」という手順を踏んでいることから、EPELとol7_developer_EPELは厳密には同じバイナリではない。またバージョン等のタイムラグの可能性がある。

依存性などの原因でol7_developer_EPELにあるパッケージのインストールに失敗するときは、EPELに切り替えてみよう。

4. さらなるEPELの使いこなし

4-1. EPELの有効化/無効化を切り替える

EPELに限らず追加のリポジトリを使用するときは、「常時有効にする」「一時的に有効にする」の二通りの方法がある。EPELを使い始めたら常時有効にするのが一般的だが、EPELのパッケージを明示的に区別したいときは「一時的に有効にする」ことで区別できる。

常時有効にする
yum repolistで表示されるときは有効になっている。またrepoファイルはenabled=1になっている。

一時的に有効にする
この方法ではEPELを無効化しておき、必要なときだけ有効化する。

  1. EPELを無効化する。これでyumを実行してもEPELのパッケージは利用できない。
sudo yum-config-manager --disable epel

2.EPELにあるパッケージをインストール/アップデートするときだけ--enablerepoオプションで有効化する。

sudo yum --enablerepo=epel install <パッケージ名>

4-2. トラブルシュート

余力のあるとき執筆予定

5. まとめ

  • 標準リポジトリにないパッケージがあるときはEPELを探そう
  • 野良リポジトリは使わないこと。使うときは人柱覚悟で
  • RPMパッケージの依存性を崩すような強引なことはしないこと
  • AWSではEPELを有効にする独自コマンドが提供されている
  • Oracle Cloud InfrastructureのOracle Linux 7では独自のEPELリポジトリが提供されている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】AmazonLinuxのyumができない場合の対処

はじめに

AmazonLinux構築中にyum updateができない事象が発生しました。
この場合の原因と対処についてアウトプットしていきたいと思います。

事象

AmazonLinuxにてyum updateを実施したところ、下記画面にて止まることを確認。

[root@ip-192-168-5-129 ec2-user]# yum -y update
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd

環境

自宅環境

項目 説明
自宅PC  Windows10
ターミナル TeraTerm

クラウド環境

項目 説明
PublicCloud  AWS
OS Amazon Linux 2 AMI (HVM), SSD Volume Type

※自宅PC→構築したEC2に接続できるようにVPC設定済み

原因

「DNSホスト名」「DNS解決」が有効化されていない。

対策

「DNSホスト名」「DNS解決」の有効化

手順

DNS解決の編集

  • 対象VPCを右クリックし、「DNS解決の編集」をクリック

DNS解決.jpg

  • 「DNS解決」の有効化に☑を入れ、OKをクリック

2.JPG

  • 「DNS解決が更新されました」と表示されることを確認

3.JPG

DNSホスト名の編集

  • 対象VPCを右クリックし、「DNSホスト名の編集」をクリック

DNSホスト名.jpg

-「DNSホスト名」の有効化に☑を入れ、OKをクリック

4.JPG

-「DNSホスト名が更新されました」と表示されることを確認

5.JPG

AmazonLinuxにてyum update実施

実行できることを確認。

[root@ip-192-168-5-129 ec2-user]# yum -y update
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                               | 3.7 kB     00:00
amzn2extra-docker                                        | 3.0 kB     00:00
(1/5): amzn2-core/2/x86_64/group_gz                        | 2.5 kB   00:00
(2/5): amzn2-core/2/x86_64/updateinfo                      | 218 kB   00:00
(3/5): amzn2extra-docker/2/x86_64/updateinfo               |   69 B   00:00
(4/5): amzn2extra-docker/2/x86_64/primary_db               |  68 kB   00:00
(5/5): amzn2-core/2/x86_64/primary_db                      |  41 MB   00:01
Resolving Dependencies
--> Running transaction check
---> Package amazon-linux-extras.noarch 0:1.6.10-1.amzn2 will be updated
---> Package amazon-linux-extras.noarch 0:1.6.11-1.amzn2 will be an update
---> Package amazon-linux-extras-yum-plugin.noarch 0:1.6.10-1.amzn2 will be updated
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS初心者】AWS Batchで作られるEC2インスタンスがECSクラスターに入らない!ハマったのでメモ

はじめに

タイトルの通り、"AWS Batchで作られるEC2インスタンスがECSクラスターに入らない!"というハマり方をしました。
今後同じようになった人の解決の助けになればと思います。

1つ目のハマったところ → 原因: AWS Batchで選択しEC2たインスタンスタイプが東京リージョン未対応だったため

現象

AWS BatchにてCompute environments、Job queue, Job definitionsを作成しJob実行後、"RUNNABLE"の状態で止まったままでした。
このとき、EC2インスタンスはできていませんでした。AutoScalingGroupの設定を見ると下記スクショのようなエラーメッセージが出ていることがわかりました。

スクリーンショット 2020-06-14 18.42.21.png

エラーメッセージテキスト

a user request created an AutoScalingGroup changing the desired capacity from 0 to 1.
an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 0 to 1

原因と解決

これはAWS Batchで選択しEC2たインスタンスタイプ(a1系)が東京リージョン未対応だったことが原因でした。

[EC2]東京リージョンで構築可能なインスタンスタイプのアベイラビリティーゾーン別一覧表の記事を参考に対応しているインスタンスタイプを選ぶようにAWS Batchのompute environmentsを再設定するとこのエラーが解決され、次実行時にはEC2インスタンスが作成されました。

スクリーンショット 2020-06-14 18.51.21.png

2つの目のハマったところ → 原因: Compute environmentsで選択したサブネットが外部ネットワークに接続できない状態だった

現象

1つ目の問題を解決でき、EC2インスタンスができましたが、そのEC2インスタンスがAWS Batchが管理するECSクラスター内に入らずBatchのジョブがRUNNABLEのままという状況になっていました。

原因と解決

調査

公式のAWS Batch ジョブ が RUNNABLE ステータスで止まっているのはなぜですか?の記事を確認しましたが、この記事上の項目はすべて問題なしとわかり、

それでもインスタンスが Amazon ECS クラスターに参加していない場合は、インスタンスに接続します。次に、 Docker デーモン と Amazon ECS コンテナーエージェントのステータスを確認します。

この手順を実施しました。

AWS Batchが作成したEC2インスタンスは Amazon ECS コンテナーエージェントなるDockerコンテナを自身に立ち上げこれが動くことがECSクラスターで動くことを意味していると知りました。

踏み台サーバを経由しAWS Batchが立ち上げたEC2インスタンスへSSHしDockerコンテナの状況を確認しました。

$ docker ps

これを確認すると、Dockerコンテナが作られては無くなっていることがわかりました。

そこで立ち上がっているときにコンテナ内のログを確認すると下記のようなエラーが出ているとわかりました。

$ docker logs -f ecs-agent
level=info time=2020-06-22T13:06:09Z msg="Loading configuration" module=agent.go
level=info time=2020-06-22T13:06:09Z msg="Image excluded from cleanup: amazon/amazon-ecs-agent:latest" module=parse.go
level=info time=2020-06-22T13:06:09Z msg="Image excluded from cleanup: amazon/amazon-ecs-pause:0.1.0" module=parse.go
level=info time=2020-06-22T13:06:09Z msg="Amazon ECS agent Version: 1.36.2, Commit: 0e4174f6" module=agent.go
level=info time=2020-06-22T13:06:09Z msg="Creating root ecs cgroup: /ecs" module=init_linux.go
level=info time=2020-06-22T13:06:09Z msg="Creating cgroup /ecs" module=cgroup_controller_linux.go
level=info time=2020-06-22T13:06:09Z msg="Event stream ContainerChange start listening..." module=eventstream.go
level=info time=2020-06-22T13:06:09Z msg="Loading state!" module=state_manager.go
level=info time=2020-06-22T13:06:10Z msg="Registering Instance with ECS" module=agent.go
level=info time=2020-06-22T13:06:10Z msg="Remaining mem: 3725" module=client.go
level=error time=2020-06-22T13:06:31Z msg="Unable to register as a container instance with ECS: RequestError: send request failed\ncaused by: Post https://ecs.ap-northeast-1.amazonaws.com/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)" module=client.go
level=error time=2020-06-22T13:06:31Z msg="Error registering: RequestError: send request failed\ncaused by: Post https://ecs.ap-northeast-1.amazonaws.com/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)" module=agent.go

明らかにネットワークの問題っぽい文言。

sudo yum updateを実行すると実行がされない。。ネットワークの問題なのが確定です。

ECSインスタンスがクラスターに登録されないの記事に出会い、ネットワークの様子を見ると私の場合はルーティングテーブルの箇所が悪くAWS BatchのCompute environmentsが持つサブネットが外部ネットワークにつながらないものだったことが原因とわかりました。

ポイント

  • AWS Batchが作成するEC2インスタンスはPublic IPを割り当てられない
  • インターネットゲートウェイをルーティングテーブルに持つサブネットに入っていてもそのEC2がPublic IPを持っていないとインターネットにはつながらない

解決方法

そのため、公式のパブリックサブネットとプライベートサブネットを持つ VPC (NAT)の記事を参考にNATゲートウェイがTargetになるルーティングテーブルを作成。そのルーティングテーブルを持つサブネットを作成し、そのサブネットをAWS BatchのCompute environmentsに紐付けると無事にJobが走るようになりました。

スクリーンショット 2020-06-23 01.20.41.png

参考

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

Next.jsを使って、医師国家試験の勉強ノートを公開するブログを作った話

Next.jsを使って、医師国家試験の勉強ノートを公開するブログを作った話

表題の通りです。まずは完成品とレポジトリのURLをどうぞ。

サイト:https://chilvary-beta.vercel.app

GitHub:https://github.com/yokonao/chilvary-beta

作ったきっかけ

next.jsの公式チュートリアル(https://nextjs.org/learn/basics/create-nextjs-app?utm_source=next-site&utm_medium=homepage-cta&utm_campaign=next-website)をやったら、構築の容易さとSPAのページ遷移の速さに感動したので。これで自分のブログ作ったら超便利じゃない?と思いました。単純なページ公開だけならバックエンドなしでも可能です。

アーキテクチャ

アーキテクチャっていうのは大げさすぎるかもしれません。

  1. ローカルPCで書いたMarkdownファイルを作成
  2. MarkdownファイルをAWS S3にアップロード
  3. S3のデータを引っ張ってきて、静的サイトとしてビルド

Next.jsとは全く関係ありませんが、Markdownファイルのバージョン管理とアップロードは完全自動化しています。作業終了後Alfredをちょっと立ち上げてAppleScriptを起動すればアップロードが完了するようにしました。(AppleScriptでgit pushしてgithub actionsでS3にアップロード)。この話はまた別でしたい。

メリット

  • SPAなのでページ遷移はほぼ0秒
  • 超優秀なMarkdownエディタTyporaの力を借りることができる

参考:https://qiita.com/4_mio_11/items/223326c3289f6b2c2a07

実際の記事データ

こんな感じになります。

クエスチョンバンク(QB)を解きながら、疑問に思った点を調べてMarkdownファイルにまとめる。国試の勉強は長期戦なので、1年後の自分に向けて書いてます。

スタイルに関しては、Typoraのテーマで使用されているcssファイルとBulmaを併用してます。記事をローカルで作成した時とほぼ同じ見た目で公開しつつ、ヘッダーなどを作る際はBulmaのコンポーネントを利用できるになっています。

チュートリアルから進化している点

S3のディレクトリ構成が完成品のブログにそのまま反映されています。説明が難しいですがサイトを一度巡回していただければ、意図が伝わるはず...

本当に手元で作成したフォルダがそのままwebサイトになるイメージです。

Next.jsのすごいところ

このwebサイト、markdownファイルの中身をNext.jsに渡してあげれば成立するので以下のような拡張もできると思います。

  • データの置き場所をS3からデータベースにする

  • Markdown形式で記事をアップロードできるwebアプリを作成してS3を介してつなぐ

メディアサイトのようなものを作成しようとすると、認証機能など難しい点も出てくるでしょう。しかし、ローカルで作成した文書をSPAとして公開できるサイトをこれだけ簡単に自分で1から作れてしまうのは、Next.js様様かな、と感じます。もちろんGitHub Pagesなどもっと楽な選択肢もありますが、SPAや自由にカスタマイズできるというメリットは十分あると思います。

個人的には自分で見返す際もページ遷移に時間をとられないのでストレスフリーで気持ちいいです。普段やる気にならない勉強も記事作成という遊び感覚でできるようになりましたし。

## 今後

現在は、ビルド時に全てのページを静的に作成しているので、記事を追加・変更する際は再ビルドが必要になります。

しかし最近、Next.jsは静的なwebサイトに動的なページ更新機能を搭載できるようになりました。Incremental Static Regenerationというやつです。この機能を使いたかったのですが、現在はバグが存在するようなので実現できませんでした。

Next.jsのレポジトリではこのバグに関するissueに最優先事項のラベルがつけられていたので、修正がきたら導入したいと思っています。これさえ導入できれば、Markdownファイルの保存から公開までがほぼ自動化できる...

補足

Incremental Static Regenerationについては以下のサイトの説明が超わかりやすいです。(英語ですけど...)

https://arunoda.me/blog/what-is-nextjs-issg

件のバグのissue

https://github.com/vercel/next.js/issues/12851

https://github.com/vercel/next.js/issues/14382

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

Next.jsを使って医師国家試験の勉強ノートを公開するブログを作った話

Next.jsを使って医師国家試験の勉強ノートを公開するブログを作った話

表題の通りです。まずは完成品とレポジトリのURLをどうぞ。

サイト:https://chilvary-beta.vercel.app

GitHub:https://github.com/yokonao/chilvary-beta

作ったきっかけ

next.jsの公式チュートリアル(URLは以下)をやったら、構築の容易さとSPAのページ遷移の速さに感動したので。これで自分のブログ作ったら超便利じゃない?と思いました。単純なページ公開だけならバックエンドなしでも可能です。
https://nextjs.org/learn/basics/create-nextjs-app?utm_source=next-site&utm_medium=homepage-cta&utm_campaign=next-website

アーキテクチャ

アーキテクチャっていうのは大げさすぎるかもしれません。

  1. ローカルPCで書いたMarkdownファイルを作成
  2. MarkdownファイルをAWS S3にアップロード
  3. S3のデータを引っ張ってきて、静的サイトとしてビルド

Next.jsとは全く関係ありませんが、Markdownファイルのバージョン管理とアップロードは完全自動化しています。作業終了後Alfredをちょっと立ち上げてAppleScriptを起動すればアップロードが完了するようにしました。(AppleScriptでgit pushしてgithub actionsでS3にアップロード)。この話はまた別でしたい。

メリット

  • SPAなのでページ遷移はほぼ0秒
  • 超優秀なMarkdownエディタTyporaの力を借りることができる

参考:https://qiita.com/4_mio_11/items/223326c3289f6b2c2a07

実際の記事にはどんなこと書いてんの?

クエスチョンバンク(QB)を解きながら、疑問に思った点を調べてMarkdownファイルにまとめる。国試の勉強は長期戦なので、1年後の自分に向けて書いてます。

スタイルに関しては、Typoraのテーマで使用されているcssファイルとBulmaを併用してます。記事をローカルで作成した時とほぼ同じ見た目で公開しつつ、ヘッダーなどを作る際はBulmaのコンポーネントを利用できるになっています。

チュートリアルから進化している点

S3のディレクトリ構成が完成品のブログにそのまま反映されています。説明が難しいですがサイトを一度巡回していただければ、意図が伝わるはず...

本当に手元で作成したフォルダがそのままwebサイトになるイメージです。

Next.jsのすごいところ

このwebサイト、markdownファイルの中身をNext.jsに渡してあげれば成立するので以下のような拡張もできると思います。

  • データの置き場所をS3からデータベースにする

  • Markdown形式で記事をアップロードできるwebアプリを作成してS3を介してつなぐ

メディアサイトのようなものを作成しようとすると、認証機能など難しい点も出てくるでしょう。しかし、ローカルで作成した文書をSPAとして公開できるサイトをこれだけ簡単に自分で1から作れてしまうのは、Next.js様様かな、と感じます。もちろんGitHub Pagesなどもっと楽な選択肢もありますが、SPAや自由にカスタマイズできるというメリットは十分あると思います。

個人的には自分で見返す際もページ遷移に時間をとられないのでストレスフリーで気持ちいいです。普段やる気にならない勉強も記事作成という遊び感覚でできるようになりましたし。

今後

現在は、ビルド時に全てのページを静的に作成しているので、記事を追加・変更する際は再ビルドが必要になります。

しかし最近、Next.jsは静的なwebサイトに動的なページ更新機能を搭載できるようになりました。Incremental Static Regenerationというやつです。この機能を使いたかったのですが、現在はバグが存在するようなので実現できませんでした。

Next.jsのレポジトリではこのバグに関するissueに最優先事項のラベルがつけられていたので、修正がきたら導入したいと思っています。これさえ導入できれば、Markdownファイルの保存から公開までがほぼ自動化できる...

補足

Incremental Static Regenerationについては以下のサイトの説明が超わかりやすいです。(英語ですけど...)

https://arunoda.me/blog/what-is-nextjs-issg

件のバグのissue

https://github.com/vercel/next.js/issues/12851

https://github.com/vercel/next.js/issues/14382

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

【AWS;Lambda入門】第二弾;jsonファイルから文章抽出してS3保存♬

前回は、以下のコードでs3://バケットに配置されたmp3ファイルをtranscribeしてテキストに変換して、jsonファイルをOutputBucketNameのS3;バケットに配置した。
今回は、このjsonファイルを呼び出して、テキスト変換された文章を抽出しようと思います。わざわざ前回コードを出したのは、今回もコードが似ているからです。

s3 = boto3.client('s3')
transcribe = boto3.client('transcribe')
def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    try:
        transcribe.start_transcription_job(
            TranscriptionJobName= datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '_Transcription',
            LanguageCode='ja-JP',
            Media={
                'MediaFileUri': 'https://s3.ap-northeast-1.amazonaws.com/' + bucket + '/' + key
            },
            OutputBucketName='lamoutput'
        )
...
        raise e

ということで、以下のコードで実施できました。
S3;バケットに保存する方法は参考のとおりにしています。
【参考】
【AWS Lambdaの基本コードその2】 S3へのファイル保存
Boto3 で S3 のオブジェクトを操作する(高レベルAPIと低レベルAPI)
参考①のコメントを残しています。ほぼまんまなコードで動きました。
異なるのは、先日のjsonファイルの取り扱い方を取り入れている部分です。
まず、Libは以下のとおり、

# ①ライブラリのimport
import boto3
import urllib.parse
from datetime import datetime
import json

以下は、参考②から真似してclientを定義しています。

print('Loading function')      # ②Functionのロードをログに出力
s3 = boto3.resource('s3')      # ③S3オブジェクトを取得
client = s3.meta.client

lambda_handlerの入りのbucket, keyの取得は、上記のtranscribeのコードと全く同一です(当然ですが。。)。

# ④Lambdaのメイン関数
def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

以下は、参考②と同じコードでjsonファイルから、response['Body']を読込ます。
ところが、ここで躓きました。
つまり、このbody.decode('utf-8')とすれば日本語の文章が表れると思っておりました。が、現実は結構なjson likeな(文字列)が出現。
当初は、これは文字列と気づかず、jsonファイルだと思いました。
ということで、文字列と気づき、さらにjson.loadsでjsonファイルに変換できることが分かり、...やっと以下のコードにたどり着きました。
つまり、bodyは文字列です。

    response = client.get_object(Bucket=bucket, Key=key)
    body = response['Body'].read()

文字列をjsonファイルに変換します。

    dec = json.loads(body)

そして、jsonファイルなるがゆえに以下のように日本語文章が簡単に抽出できました。

    con_el=dec["results"]["transcripts"][0]["transcript"]
    print('contents=',con_el)

contents= こんにちは 東京 横浜 も 少し 曇り です 声 は 水木 さん です
最後に以下のように指定したs3;バケットにkeyのような時刻入りの.txtとして保存できます。

    bucket = 'muauanpub'    # ⑤バケット名を指定
    key = 'test_' + datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '.txt'  # ⑥オブジェクトのキー情報を指定
    file_contents = con_el # 'Lambda test'  # ⑦ファイルの内容
    obj = s3.Object(bucket,key)     # ⑧バケット名とパスを指定
    obj.put( Body=file_contents )   # ⑨バケットにファイルを出力
    return

まとめ

・音声ファイルを変換したjsonファイルから文章抽出してs3バケットに保管できた
・これで二段階になっていますが、mp3ファイルをs3バケットに置くと、自動的にそのテキスト変換された文章そのものがs3バケットに保存されるようになりました。
・一応、Teraterm→ec2→s3バケット転送...s3バケットからダウンロード⇒表示は出来ました

・あと、このs3バケットに音声ファイルを転送するアプリとs3バケットのテキストファイルを表示するアプリが出来るとより使いやすい音声ファイル-テキスト変換アプリが出来そうです(Web化)
・変換時間が長くともどちらのLambda関数も非同期起動なので、お金にも時間にも優しいアプリになりそうです

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

TerraformでAWS VPCを作成する

TerraformでAWS VPCを作成するコード

実行環境

  • Windows 10 Home (1919)
  • Git Bash (git version 2.25.1.windows.1)
  • AWS CLI (aws-cli/2.0.3 Python/3.7.5 Windows/10 botocore/2.0.0dev7)
  • Terraform (v0.12.26)

作成する構成

まっさらな環境にVPCを1つ作成

20200623.PNG

main.tf

main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.10.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC"
    CostGroup = "prj01"
  }
}

公式サイトのマニュアルはこちら
AWS: aws_vpc - Terraform by HashiCorp

実行

実行前の状態確認

$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": []
}

VPCは1つも存在していないことを確認。

前提

$ aws configure list --profile prj01-profile
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile            prj01-profile           manual    --profile
access_key     ****************FCES shared-credentials-file
secret_key     ****************4Idw shared-credentials-file
    region                us-west-2      config-file    ~/.aws/config

前提としてaws cliのprofileは作成済み。

まずinit

$ ../terraform.exe init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.67.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.67"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

次にplan

$ ../terraform.exe plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be created
  + resource "aws_vpc" "prj01VPC" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.10.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "CostGroup" = "prj01"
          + "Name"      = "prj01VPC"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

確認ポイント
- 今回は新規作成なので、全て「+」になっていること
- changeとdestroyが「0」になっていること
- その他エラーや警告が発生していないこと

確認できたのでapply

$ ../terraform.exe apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be created
  + resource "aws_vpc" "prj01VPC" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.10.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "CostGroup" = "prj01"
          + "Name"      = "prj01VPC"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.prj01VPC: Creating...
aws_vpc.prj01VPC: Still creating... [10s elapsed]
aws_vpc.prj01VPC: Creation complete after 13s [id=vpc-085c4a097408d438d]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

確認ポイント
- Apply complete!と表示されること
- added, changed, destroyedが想定通りであること(今回は1,0,0であること)

実行後の確認

$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.10.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-085c4a097408d438d",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-05db0b29ba54e1edc",
                    "CidrBlock": "10.10.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC"
                }
            ]
        }
    ]
}

成功!

失敗パターン

providerにregionを指定しないと

main.tf
provider "aws" {
  profile = "prj01-profile"
}
$ ../terraform.exe plan
provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Enter a value: 

上記のようにリージョンの指定を求められてしまうため、main.tf等に記載しておく方が良い。

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

【AWS】GithubからCodePipelineでECS/Fargateにデプロイする方法

【AWS】GithubからCodePipelineでECS/Fargateにデプロイする方法

CodePipelineを使ってECS/Fargateにnginx,phpコンテナをデプロイする方法をまとめます。

目次


構築イメージ

ZAC_Infra_Diagram-Qiita_20200624_Infra.png

動作環境・前提条件

【動作環境】
OS : macOS 10.14.6

【前提条件】
VPC,サブネットは作成済

STEP1. ECRの作成とイメージのプッシュ

ECR(Amazon Elastic Container Registry)とはDocker Hubのようなコンテナイメージを保管するAWS上のレジストリのことです.

ECRの作成

image.png

複数のイメージコンテナをデプロイするため、レジストリも複数作成します.

image.png

リポジトリ名は任意で構いませんが、後ほどdockerイメージとタグ付けを行うため,管理しやすいようdockerfileで定義したコンテナ名と同じ名前をつけています.

イメージのプッシュ

次にそれぞれのリポジトリ名をクリックし、リポジトリに入り、プッシュコマンドの表示を押します. 

表示されたコマンドを手元のMACのターミナルで実行します.

image.png

実行後、ECRにlatestタグが付いたイメージがアップロードされます.

image.png

STEP2. タスク定義ファイルとAppspecファイルの作成

タスク定義ファイル

AWSのチュートリアルではtaskdef.jsonと記載されていますが、
複数コンテナを同じタスクに登録する場合,この時点ではテンプレートを作成し、codebuildの際にtaskdef.jsonを作成するように設定します.

taskdef-template.json
{
    "executionRoleArn": "arn:aws:iam::アカウントID:role/ecsTaskExecutionRole",
    "containerDefinitions": [{
            "name": "nginx",
            "image": "<IMAGE_NGINX_NAME>",
            "essential": true,
            "portMappings": [{
                "hostPort": 80,
                "protocol": "tcp",
                "containerPort": 80
            }]
        },
        {
            "name": "php",
            "image": "<IMAGE_PHP_NAME>",
            "essential": true,
            "portMappings": [{
                "hostPort": 9000,
                "protocol": "tcp",
                "containerPort": 9000
            }]
        }
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "networkMode": "awsvpc",
    "cpu": "256",
    "memory": "512",
    "family": "ecs-task"
}

Appspecファイル

AppspecファイルはCodeDeployの際に実行されます.

appspec.yml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION> 
        #<TASK_DEFINITION>は変えずにそのままにする
        LoadBalancerInfo:
          ContainerName: "nginx"
          ContainerPort: 80

作成したタスク定義ファイルとAppspecファイルをプロジェクト直下のフォルダに保存し、githubにpushします.

STEP3. アプリケーションロードバランサーとターゲットグループを作成する

アプリケーションロードバランサとターゲットグループをAWSチュートリアルに沿って作成します.
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html#tutorials-ecs-ecr-codedeploy-loadbal

STEP4. ECSクラスターとサービスを作成する

ECSクラスターとサービスも上記リンクのSTEP4と同じように作成します.

参考までに作成したサービス作成ファイルを掲示します.

create-service.json
{
    "taskDefinition": "ecs-task",
    "cluster": "ecs-cluster01",
    "loadBalancers": [{
        "targetGroupArn": "AWSのサイトで確認したtargetgroupのArnを入力",
        "containerName": "nginx",
        "containerPort": 80
    }],
    "desiredCount": 1, #タスク実行数を定義しています.
    "launchType": "FARGATE",
    "schedulingStrategy": "REPLICA",
    "deploymentController": {
        "type": "CODE_DEPLOY"
    },
    "networkConfiguration": {
        "awsvpcConfiguration": {
            "subnets": [
                "subnet-0fXXXXXのようなsubnet IDを記載",
                "subnet-0fXXXXXのような2つ目のsubnet IDを記載"
            ],
            "securityGroups": [
                "security gropuのIDを記載"
            ],
            "assignPublicIp": "ENABLED"
        }
    }
}

こちらもプロジェクトフォルダに保存し、ローカルPCのターミナルからコマンドを実行,サービスを作成します.
*ecs-test-serviceはサービス名のため任意の名前を付けられます

aws ecs create-service --service-name ecs-test-service --cli-input-json file://create-service.json

STEP5. CodeDeployアプリケーションとデプロイグループを作成

こちらもAWSチュートリアルに従って作成していきます
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html#tutorials-ecs-ecr-codedeploy-cluster

注釈

まずは動かして動作を確かめる場合はチュートリアル通りで問題ないです。

ただチュートリアルだとすぐにトラフィックを再ルーティングとなっておりデプロイした新しいターゲットグループに対してすぐにユーザがアクセスできるようになっています。

またデフォルトでは1時間後に、古いリビジョンが消去されるようになっているため、こちらも運用ポリシーに沿って変更します。

こちらではどちらも1日と設定しています。

image.png

STEP6. CodeBuildを作成

CodeBuildを実行するためにBuildspecファイルを作成し、プロジェクトフォルダのルートに保存します.そして、GithubにPushします.

buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      - IMAGE_URI_WEB=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME_WEB
      - IMAGE_URI_PHP=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME_PHP
      - $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION})
  build:
    commands:
      - docker-compose -f docker-compose-production.yml build
      - docker tag src_web:$IMAGE_TAG $IMAGE_URI_WEB:$IMAGE_TAG
      - docker tag src_php:$IMAGE_TAG $IMAGE_URI_PHP:$IMAGE_TAG
  post_build:
    commands:
      - docker push $IMAGE_URI_WEB:$IMAGE_TAG
      - docker push $IMAGE_URI_PHP:$IMAGE_TAG
      #taskdef-templateで定義したIMAGE_URIを環境変数で置き換えています.
      - cat taskdef-template.json | sed -e s@\<IMAGE_WEB_NAME\>@$IMAGE_URI_WEB:$IMAGE_TAG@ -e s@\<IMAGE_PHP_NAME\>@$IMAGE_URI_PHP:$IMAGE_TAG@ > taskdef.json
artifacts:
  files:
    - appspec.yaml
    - taskdef.json

その後、AWSの管理画面からCodeBuildの作成を選択します。
作成時に環境変数を設定する画面があるためにそこにリポジトリ名などを入れます。
そして、ソースとしてGithubを選択します.

アーティファクトも不要です.

image.png

ポイント1. 特権を付与

こちらを必ずチェックします.
image.png

そしてBuildの作成を実行します.

ポイント2. CodeBuildにポリシーを付与

codebuild作成後、CodeBuildがECRにアクセスできるようにCodeBuildのサービスロールにAmazonEC2ContainerRegistryPowerUserポリシーをアタッチします.

STEP7. CodePipelineを作成

最後にCodePipelineの作成を選択し、ソースにはGithub, ビルドステップではSTEP5で作成したCodeBuildを選択します。

デプロイステップではAmazon ECS(ブルー/グリーン)を選択し、
Source Artifactでtaskdef.jsonとappspec.ymlを選択します。

タスク定義の動的な更新イメージ - オプショナルは入力不要です。

 動作確認

作成が完了するとCodePipelineがスタートします。

ここでDeployのところでステータスが処理中になります。

そのため、ロードバランサのDNS:8080(テスト用のリスナー)にアクセスに新しくデプロイしたシステムが問題なく動くか確認します。

問題なさそうであればDeploy画面からトラフィックの再ルーティングをクリックすると、
新システムにトラフィックが流れ始めます。

image.png

その後、CodeDeployで設定した待機時間をすぎると古いタスクが停止されCodeDeployが成功となります。

image.png
image.png

実際にAWSの管理画面からECSを選択し、タスクが実行されていることを確認します。

そしてブラウザからロードバランサが持つ、パブリックDNSに対してアクセスし、PHPの画面が表示されれば成功です。

その後、githubに対して新たなバージョンをpushするとcodepiplineが実行されます.

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

Railsアプリに独自ドメインを与えてAWSに上げても表示されない問題

「自分のアプリを独自ドメインにしてAWSに上げたろー」

ということで以下の記事を参考に自分のアプリに独自ドメインを付与しました。
https://mel.onl/onamae-domain-aws-route-53/

しかし3日待っても全く表示されない。AWSの紹介ページが表示されるだけ・・・
そこで色々調べた結果、Nginxの設定が必要であるとわかった。

Nginxの設定

EC2で

[USERNAME|~]$ cd /etc/nginx/conf.d/
[USERNAME|conf.d]$ sudo vi 〇〇.conf (#自分のアプリケーション名)

〇〇.confで

server_name  〇〇.com;(#アプリの独自ドメインに変更してください)

保存して終了。再起動する。

[USERNAME|〇〇(アプリ名)]$ sudo nginx -s reload

これで行けるはず!

参考文献
https://qiita.com/naoki_mochizuki/items/5a1757d222806cbe0cd1#nginx%E3%81%AE%E8%B5%B7%E5%8B%95

https://teratail.com/questions/49756

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

AWSハンズオン実践メモ 〜スケーラブルウェブサイト構築編〜

はじめに

AWSでWeb三層モデルを体系的に構築した事が無かった為、AWS公式のハンズオン(スケーラブルウェブサイト構築編)を実践しました。
本記事は自身のハンズオン学習メモとして投稿します。

目次

ハンズオンの目的

WordPressを具体例に、

  • Amazon EC2
  • Amazon Virtual Private Cloud (VPC)
  • Amazon Relational Database Service (RDS)
  • Elastic Load Balancing (ELB)

を利⽤し、スケーラブルなWebシステムを構築する。

WordPressを使ったブログサイトの構築を通じて、スケーラブルな Web システム構築を学んでいただけるウェビナーシリーズです。主に取り扱う AWS サービスは Amazon EC2、Amazon RDS、ELB の3つです。また、作って終わりではなく、実際にEC2インスタンス1台を停止させ、その状態でもブログサイトにアクセスできることも確認します。

202004-handson-harada.9f9c1460b97c9eef660f9a881ea3c59f3c7ef7b7.png
(https://aws.amazon.com/jp/aws-jp-introduction/aws-jp-webinar-hands-on/?trk=aws_blog より引用)

本編

※全てマネジメントコンソールから実施。

Amazon VPCの作成

  • VPC作成
  • Subnet作成(Private、PublicをAZ-A、AZ-Cに1つずつ)
  • ルートテーブルの関連付け(PublicのデフォルトゲートウェイをIGWに設定)

Amazon EC2の作成

  • PublicのAZ-AにEC2インスタンスを作成。ユーザーデータを利用しWordPressをインストール
  • SecurityGroup作成・設定
  • EC2にSSH(EC2 Instance Connect (ブラウザベースの SSH 接続)を利用)

EC2 Instance Connectを使えばSSHの鍵を作成せずに接続出来るのか。。。
参考:EC2のSSHアクセスをIAMで制御できるEC2 Instance Connectが発表されました

Amazon RDSの作成

  • SecurityGroup作成
  • Subnetグループ作成
  • RDS作成

ELBの作成

  • SecurityGroup作成
  • ALB作成(ターゲットグループ作成、ヘルスチェック設定)
  • DNS名で接続

WordPress初期設定

初期設定を行い、アクセス。特筆すべき事はなし

AMIの作成と、そのAMIから2つ⽬のEC2インスタンス作成

  • AMI作成(既にデプロイしているEC2のAMIを作成)
  • AMIからEC2作成
  • ターゲットグループに新規EC2追加

EC2(Web)とRDS(DB)のマルチAZ化

  • RDSをマルチAZに変更

構築したアーキテクチャの可⽤性確認

  • EC2のうち1台を停止し、Webページにアクセス。問題なくアクセス出来る事を確認
  • RDSをフェールオーバーさせ、webページにアクセス。フェールオーバー中の数分はアクセス出来ないが、その後アクセス出来るようになる事を確認

作成した AWS リソースの削除

粛々と削除。特筆すべきことはなし。

おわりに

AWSでweb三層モデルを体系的に構築したのは初めてだったので、いい経験になった。
また、AWSの資格勉強のおかげで、GUIで行うレベルの設定パラメータについては理解できている事が分かった。

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

AWSのEC2内でDockerコマンドを使えないときの対処法【目的:load遂行】

はじめに

ローカルにあるファイルをEC2へ送りたかったのですが、dockerコマンドが使えませんでした。

[ec2-user@ip-xxx-xx-xx-xx ~]$ docker load < dockerfiles.tar
-bash: docker: command not found

色々試行錯誤しながら、EC2環境にdockerをインストールすることで解決できたので共有したいと思います。

手順

[ec2-user@ip-xxx-xx-xx-xx ~]$ sudo yum install -y docker

EC2環境にDockerをインストール。EC2環境で上記コマンドを叩きます。「完了しました!」が最後に表示されたら成功。

[ec2-user@ip-xxx-xx-xx-xx ~]$ sudo service docker start

dockerを起動させます(EC2インスタンスを再起動させたときはDockerが停止してしまうので、インスタンスを再起動したときは毎回このコマンドを使います)。

さあ、もう使えるだろうと思い、手始めにdockerイメージの一覧でも見るかとコマンドを叩くと

[ec2-user@ip-xxx-xx-xx-xx ~]$ docker images
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%43j432jd34fFdocker.sock/v1.26/images/json: dial unix /var/run/docker.sock: connect: permission denied

エラーが発生。

dockerグループにec2-userを追加してなかったんですね。では追加します。

[ec2-user@ip-xxx-xx-xx-xx ~]$ sudo usermod -a -G docker ec2-user

そして一度EC2から抜けます。

[ec2-user@ip-xxx-xx-xx-xx ~]$ exit

またEC2内に入ります。

nakajimakoutanoMacBook-ea:Desktop nakajimakouta$ ssh -i xxxxxxx.pem ec2-user@ホスト名

そしてdocker imagesコマンドを叩くと無事に動きました。

[ec2-user@ip-xxx-xx-xx-xx ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

本来の目的であった「ローカルにあるファイルをEC2へ送りたい」をできるかどうか試すと

[ec2-user@ip-xxx-xx-xx-xx ~]$ docker load < Dockerfile.tar
Loaded image ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[ec2-user@ip-xxx-xx-xx-xx ~]$ docker run -it xxxxxxxxxxxxx sh
/ # $ ls
bin    dev    etc    home   lib    media  mnt    opt    proc   root   run    sbin   srv    sys    Dockerfile←これ   tmp    usr    var

成功です。無事にDockerfileをEC2内へ送ることができました。

参考記事

AWSのEC2でDockerを試してみる

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

CDKを使ってWordpressをFargate+EFS環境に移行する

概要

AWS ECS Fargate1.4.0がついにEFS(Elastic File System)をサポートしました。

これまではコンテナ内にボリュームをマウントしていたため、タスク数が増えたり、タスクがリフレッシュされるとマウントしていたデータが消えてしまっていたので、Wordpressのようなローカルにファイルを保持するようなアプリケーションにFargateは適用できませんでした。

EFSを利用することで、データを永続的に保持することが可能になるため、WordpressでもFargateを利用することができます。

今回は、CDKを利用してWordpressが稼働するインフラ環境を構築しました。

インフラ構築

1. セキュリティグループを定義

RDS、EC2、EFSのセキュリティグループを作成します。

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

    const vpc = ec2.Vpc.fromLookup(this, 'VpcForSystem', { vpcName: 'vpc' });

    // RDS用のセキュリティグループ
    // ポート3306番のTCP通信のみを許可
    const dbSecurityGroup = new ec2.CfnSecurityGroup(this, 'RDSSecurityGroup', {
      groupDescription: 'RDS Secutiry Group',
      tags: 'wordpress-rds-sg',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 3306,
          toPort: 3306,
        }
      ],
      vpcId: vpc.vpcId,
    });

    // EC2用のセキュリティグループ
    // ポート22番のSSH通信のみを許可(踏み台用)
    const ec2SecurityGroup = new ec2.CfnSecurityGroup(this, 'EC2SecurityGroup', {
      groupDescription: 'EC2 Secutiry Group',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 22,
          toPort: 22,
        }
      ],
      tags: 'wordpress-ec2-sg',
      vpcId: vpc.vpcId,
    });

    // EFS用のセキュリティグループ
    // ポート3306番のTCP通信のみを許可
    const efsSecurityGroup = new ec2.CfnSecurityGroup(this, 'EFSSecurityGroup', {
      groupDescription: 'EFS Secutiry Group',
      tags: 'wordpress-efs-sg',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 2049,
          toPort: 2049,
        }
      ],
      vpcId: vpc.vpcId,
    });
  }
}

2. EC2の作成

ローカルからRDSやEFSにアクセスするための踏み台のインスタンスを作成します。

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

    const instance = new ec2.Instance(this, 'Instance', {
      instanceType: new ec2.InstanceType('t2.micro'),
      machineImage: ec2.MachineImage.latestAmazonLinux(),
      vpc,
      allowAllOutbound: true,
      instanceName: 'wordpress-bastion',
      keyName: 'wordpress-key',
      securityGroup: ec2.SecurityGroup.fromSecurityGroupId(this, 'Ec2SecurityGroupId', cdk.Fn.importValue('Ec2-SecurityGroup-GroupId')),
    });
    instance.addUserData(
      'yum -y update',
      'rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm',
      'yum -y install --enablerepo=mysql57-community mysql-community-client php-mysqlnd',
      'yum -y install amazon-efs-utils',
      `mkdir -p /mnt/efs/${FileSystemId}`,
      `mount -t efs ${cdk.Fn.importValue(FileSystemId}:/ /mnt/efs/${FileSystemId}`
    );
  }
}

3. RDSの作成

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

    // Parameter Group
    const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', {
      description: 'DB Parameter Group',
      family: 'mysql5.7',
      parameters: {
        'character_set_connection': 'utf8',
        'character_set_database': 'utf8',
        'character_set_results': 'utf8',
        'character_set_server': 'utf8',
        'character_set_client': 'utf8',
      },
    });

    // Instance
    const databaseInstance = new rds.DatabaseInstance(this, 'DbInstance', {
      allocatedStorage: 20,
      autoMinorVersionUpgrade: false,
      backupRetention: cdk.Duration.days(7),
      cloudwatchLogsRetention: logs.RetentionDays.TWO_MONTHS,
      copyTagsToSnapshot: true,
      engine: rds.DatabaseInstanceEngine.MYSQL,
      engineVersion: '5.7',
      instanceClass: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      instanceIdentifier: 'wordpress-rds',
      licenseModel: rds.LicenseModel.GENERAL_PUBLIC_LICENSE,
      masterUsername: 'wp_admin',
      masterUserPassword: this.node.tryGetContext('rds-password'),
      multiAz: false,
      parameterGroup,
      securityGroups: [
        ec2.SecurityGroup.fromSecurityGroupId(this, 'DbSecurityGroup', cdk.Fn.importValue(DBSecurityGroupId)),
      ],
      storageEncrypted: false,
      storageType: rds.StorageType.GP2,
      enablePerformanceInsights: false,
      vpc,
    });
  }
}

4. ALBの作成

export class LbStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, stackUtil: StackUtil, externalProps: IExternalProperties, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcName: stackUtil.getVpcName() });

    // Security Group for LB
    const lbSecurityGroup = new ec2.SecurityGroup(this, 'LbSecurityGroup', {
      securityGroupName: stackUtil.getName('LB-SG'),
      description: 'Security group for Loadbalancer.',
      vpc,
      allowAllOutbound: true,
    });
    lbSecurityGroup.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(80), '');
    lbSecurityGroup.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(443), '');
    stackUtil.addTagToResource(lbSecurityGroup, 'LB-SG');

    // Application Load Balancer
    const alb = new elbv2.CfnLoadBalancer(this, 'LoadBalancer', {
      name: 'wordpress-alb',
      type: 'application',
      scheme: 'internet-facing',
      subnets: vpc.publicSubnets.map(c => c.subnetId),
      securityGroups: [
        lbSecurityGroup.securityGroupId,
      ],
    });

    // Target Group for ALB
    const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
      deregistrationDelay: cdk.Duration.seconds(15),
      healthCheck: {
        healthyThresholdCount: 3,
        healthyHttpCodes: '200-299,301,302,303,304',
        interval: cdk.Duration.seconds(90),
        path: '/',
        protocol: elbv2.Protocol.HTTP,
        timeout: cdk.Duration.seconds(60),
        unhealthyThresholdCount: 3,
      },
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetGroupName: 'wordpress-tg',
      targetType: elbv2.TargetType.IP,
      vpc,
    });

    // HTTPリクエストの場合は、HTTPSにリダイレクト
    new elbv2.CfnListener(this, 'ListenerHttp', {
      port: 80,
      protocol: elbv2.Protocol.HTTP,
      loadBalancerArn: alb.ref,
      defaultActions: [
        { type: 'redirect', redirectConfig: { port: '443', protocol: elbv2.Protocol.HTTPS, statusCode: 'HTTP_301' } },
      ],
    });

    new elbv2.CfnListener(this, 'ListenerHttps', {
      port: 443,
      protocol: elbv2.Protocol.HTTPS,
      loadBalancerArn: alb.ref,
      certificates: [
        { certificateArn: '[LBの証明書ARN]', },
      ],
      defaultActions: [
        { targetGroupArn: targetGroup.targetGroupArn, type: 'forward', },
      ],
    });
  }
}

5. EFSの作成

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

    const fileSystem = new efs.FileSystem(this, 'FileSystem', {
      vpc,
      fileSystemName: 'wordpress-efs',
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      securityGroup: ec2.SecurityGroup.fromSecurityGroupId(this, 'EfsSecurityGroup', cdk.Fn.importValue('EfsSecurityGroupd')),
    });
  }
}

6. System Managerのパラメータストアにパラメータ定義

Fargateのコンテナから環境変数を参照するために、System Managerのパラメータストアにパラメータを定義しておきます。

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

    new ssm.StringParameter(this, 'WpDbHostParameter', {
      description: 'Wordpress DB Host',
      parameterName: '/wordpress/db_host',
      stringValue: '[RDSのDBエンドポイント]',
    });

    new ssm.StringParameter(this, 'WpDbPortParameter', {
      description: 'Wordpress DB Port',
      parameterName: '/wordpress/db_port',
      stringValue: '[RDSのDBポート]',
    });

    new ssm.StringParameter(this, 'WpDbPasswordParameter', {
      description: 'Wordpress DB Password',
      parameterName: '/wordpress/db_password',
      stringValue: 'password',
    });

    new ssm.StringParameter(this, 'WpDbNameParameter', {
      description: 'Wordpress DB Name',
      parameterName: '/wordpress/db_name',
      stringValue: 'wordpress',
    });

    new ssm.StringParameter(this, 'WpDbUserParameter', {
      description: 'Wordpress DB User',
      parameterName: '/wordpress/db_user',
      stringValue: 'wp_admin',
    });
  }
}

7. ECSの作成

ECSのリソースを作成します。
注意すべき点は、2020年6月現在Fargateのlatestバージョンは1.3.0ですが、1.3.0ではEFSをサポートしていないため、1.4.0を指定する必要があります。
また、CDK及びCloudformationではまだ、インフラコードからFargateとEFSの連携ができないため、手で作成する必要があります。
タスク定義をインフラコードで作成した後、コンソールから「新しいリビジョン」を選択し、EFSの追加、コンテナ上でEFSをマウントします。
コンテナパスは/var/www/htmlにします。

スクリーンショット 2020-06-23 16.21.07.png
スクリーンショット 2020-06-23 16.22.14.png

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

    // Load ECS Cluster
    const cluster = new ecs.CfnCluster(this, 'Cluster', {
      clusterName: 'wordpress-cluster',
    });

    // Task Execution Role
    const executionRole = new iam.Role(this, 'TaskExecutionRole', {
      roleName: 'wordpress-taskexecution-role',
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'),
      ],
      inlinePolicies: {
        'INLINE': new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:GetParameter',
                'ssm:GetParameters',
                'ssm:GetParameterHistory',
                'ssm:GetParametersByPath',
              ],
              resources: [
                `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/*`,
              ],
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:DescribeParameters',
              ],
              resources: [
                '*',
              ],
            }),
          ],
        }),
      },
    });

    // Task Role
    const taskRole = new iam.Role(this, 'TaskRole', {
      roleName: 'wordpress-task-role',
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      inlinePolicies: {
        'INLINE': new iam.PolicyDocument({
          statements: [                 
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:GetParameter',
                'ssm:GetParameters',
                'ssm:GetParameterHistory',
                'ssm:GetParametersByPath',
              ],
              resources: [
                `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/*`,
              ],
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:DescribeParameters',
              ],
              resources: [
                '*',
              ],
            }),
          ],
        }),
      },
    });

    // Task Definition
    let taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
      cpu: 256,
      executionRole,
      family: 'wordpress-taskdefinition',
      memoryLimitMiB: 512,
      taskRole,
    });

    // Container
    let containerDefinition = new ecs.ContainerDefinition(this, 'wordpress', {
      image: ecs.ContainerImage.fromRegistry('wordpress:latest'),
      taskDefinition,
      cpu: 256,
      environment: {
        REGION: cdk.Aws.REGION,
      },
      logging: ecs.LogDriver.awsLogs({
        streamPrefix: 'wordpress',
        logGroup,
      }),
      memoryReservationMiB: 512,
      secrets: {
        WORDPRESS_DB_HOST: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbHostParameterName', `/wordpress/db_host`),
        ),
        WORDPRESS_DB_PORT: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbPortParameterName', `/wordpress/db_port`),
        ),
        WORDPRESS_DB_PASSWORD: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbPasswordParameterName', `/wordpress/db_password`),
        ),
        WORDPRESS_DB_NAME: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbNameParameterName', `/wordpress/db_name`),
        ),
        WORDPRESS_DB_USER: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbUserParameterName', `/wordpress/db_user`),
        ),
      }
    });
    containerDefinition.addPortMappings({
      containerPort: 80,
      hostPort: 80,
      protocol: ecs.Protocol.TCP,
    });

    // Service
    const fargateService = new ecs.CfnService(this, 'EcsService', {
      serviceName: 'wordpress-service',
      cluster: cluster.clusterArn,
      desiredCount: 1,
      launchType: ecs.LaunchType.FARGATE,
      loadBalancers: [
        {
          containerName: 'wordpress'
          containerPort: 80,
          targetGroupArn: cdk.Fn.importValue('LbTgArn'),
        },
      ],
      deploymentConfiguration: {
        maximumPercent: 200,
        minimumHealthyPercent: 100,
      },
      networkConfiguration: {
        awsvpcConfiguration: {
          securityGroups: [
            systemSecurityGroup.securityGroupId,
          ],
          subnets: vpc.publicSubnets.map(c => c.subnetId),
          assignPublicIp: 'ENABLED',
        },
      },
      platformVersion: '1.4.0', // TODO: latest
      healthCheckGracePeriodSeconds: 30,
      taskDefinition: taskDefinition.taskDefinitionArn,
    });
  }
}

まとめ

Fargateを利用することでログの可視化(Cloudwatch)や、セキュリティ強化のためにWAFを導入したり、死活監視でXrayを使ったりなど、アーキテクチャの幅が広がりそうです。

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

CDKでWordpressをFargate+EFS環境に移行する

概要

AWS ECS Fargate1.4.0がついにEFS(Elastic File System)をサポートしました。

これまではコンテナ内にボリュームをマウントしていたため、タスク数が増えたり、タスクがリフレッシュされるとマウントしていたデータが消えてしまっていたので、Wordpressのようなローカルにファイルを保持するようなアプリケーションにFargateは適用できませんでした。

EFSを利用することで、データを永続的に保持することが可能になるため、WordpressでもFargateを利用することができます。

今回は、CDKを利用してWordpressが稼働するインフラ環境を構築しました。

インフラ構築

1. セキュリティグループを定義

RDS、EC2、EFSのセキュリティグループを作成します。

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

    const vpc = ec2.Vpc.fromLookup(this, 'VpcForSystem', { vpcName: 'vpc' });

    // RDS用のセキュリティグループ
    // ポート3306番のTCP通信のみを許可
    const dbSecurityGroup = new ec2.CfnSecurityGroup(this, 'RDSSecurityGroup', {
      groupDescription: 'RDS Secutiry Group',
      tags: 'wordpress-rds-sg',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 3306,
          toPort: 3306,
        }
      ],
      vpcId: vpc.vpcId,
    });

    // EC2用のセキュリティグループ
    // ポート22番のSSH通信のみを許可(踏み台用)
    const ec2SecurityGroup = new ec2.CfnSecurityGroup(this, 'EC2SecurityGroup', {
      groupDescription: 'EC2 Secutiry Group',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 22,
          toPort: 22,
        }
      ],
      tags: 'wordpress-ec2-sg',
      vpcId: vpc.vpcId,
    });

    // EFS用のセキュリティグループ
    // ポート3306番のTCP通信のみを許可
    const efsSecurityGroup = new ec2.CfnSecurityGroup(this, 'EFSSecurityGroup', {
      groupDescription: 'EFS Secutiry Group',
      tags: 'wordpress-efs-sg',
      securityGroupIngress: [
        {
          ipProtocol: 'tcp',
          cidrIp: '0.0.0.0/0',
          fromPort: 2049,
          toPort: 2049,
        }
      ],
      vpcId: vpc.vpcId,
    });
  }
}

2. EC2の作成

ローカルからRDSやEFSにアクセスするための踏み台のインスタンスを作成します。

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

    const instance = new ec2.Instance(this, 'Instance', {
      instanceType: new ec2.InstanceType('t2.micro'),
      machineImage: ec2.MachineImage.latestAmazonLinux(),
      vpc,
      allowAllOutbound: true,
      instanceName: 'wordpress-bastion',
      keyName: 'wordpress-key',
      securityGroup: ec2.SecurityGroup.fromSecurityGroupId(this, 'Ec2SecurityGroupId', cdk.Fn.importValue('Ec2-SecurityGroup-GroupId')),
    });
    instance.addUserData(
      'yum -y update',
      'rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm',
      'yum -y install --enablerepo=mysql57-community mysql-community-client php-mysqlnd',
      'yum -y install amazon-efs-utils',
      `mkdir -p /mnt/efs/${FileSystemId}`,
      `mount -t efs ${cdk.Fn.importValue(FileSystemId}:/ /mnt/efs/${FileSystemId}`
    );
  }
}

3. RDSの作成

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

    // Parameter Group
    const parameterGroup = new rds.ParameterGroup(this, 'ParameterGroup', {
      description: 'DB Parameter Group',
      family: 'mysql5.7',
      parameters: {
        'character_set_connection': 'utf8',
        'character_set_database': 'utf8',
        'character_set_results': 'utf8',
        'character_set_server': 'utf8',
        'character_set_client': 'utf8',
      },
    });

    // Instance
    const databaseInstance = new rds.DatabaseInstance(this, 'DbInstance', {
      allocatedStorage: 20,
      autoMinorVersionUpgrade: false,
      backupRetention: cdk.Duration.days(7),
      cloudwatchLogsRetention: logs.RetentionDays.TWO_MONTHS,
      copyTagsToSnapshot: true,
      engine: rds.DatabaseInstanceEngine.MYSQL,
      engineVersion: '5.7',
      instanceClass: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      instanceIdentifier: 'wordpress-rds',
      licenseModel: rds.LicenseModel.GENERAL_PUBLIC_LICENSE,
      masterUsername: 'wp_admin',
      masterUserPassword: this.node.tryGetContext('rds-password'),
      multiAz: false,
      parameterGroup,
      securityGroups: [
        ec2.SecurityGroup.fromSecurityGroupId(this, 'DbSecurityGroup', cdk.Fn.importValue(DBSecurityGroupId)),
      ],
      storageEncrypted: false,
      storageType: rds.StorageType.GP2,
      enablePerformanceInsights: false,
      vpc,
    });
  }
}

4. ALBの作成

export class LbStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, stackUtil: StackUtil, externalProps: IExternalProperties, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcName: stackUtil.getVpcName() });

    // Security Group for LB
    const lbSecurityGroup = new ec2.SecurityGroup(this, 'LbSecurityGroup', {
      securityGroupName: stackUtil.getName('LB-SG'),
      description: 'Security group for Loadbalancer.',
      vpc,
      allowAllOutbound: true,
    });
    lbSecurityGroup.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(80), '');
    lbSecurityGroup.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(443), '');
    stackUtil.addTagToResource(lbSecurityGroup, 'LB-SG');

    // Application Load Balancer
    const alb = new elbv2.CfnLoadBalancer(this, 'LoadBalancer', {
      name: 'wordpress-alb',
      type: 'application',
      scheme: 'internet-facing',
      subnets: vpc.publicSubnets.map(c => c.subnetId),
      securityGroups: [
        lbSecurityGroup.securityGroupId,
      ],
    });

    // Target Group for ALB
    const targetGroup = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
      deregistrationDelay: cdk.Duration.seconds(15),
      healthCheck: {
        healthyThresholdCount: 3,
        healthyHttpCodes: '200-299,301,302,303,304',
        interval: cdk.Duration.seconds(90),
        path: '/',
        protocol: elbv2.Protocol.HTTP,
        timeout: cdk.Duration.seconds(60),
        unhealthyThresholdCount: 3,
      },
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetGroupName: 'wordpress-tg',
      targetType: elbv2.TargetType.IP,
      vpc,
    });

    // HTTPリクエストの場合は、HTTPSにリダイレクト
    new elbv2.CfnListener(this, 'ListenerHttp', {
      port: 80,
      protocol: elbv2.Protocol.HTTP,
      loadBalancerArn: alb.ref,
      defaultActions: [
        { type: 'redirect', redirectConfig: { port: '443', protocol: elbv2.Protocol.HTTPS, statusCode: 'HTTP_301' } },
      ],
    });

    new elbv2.CfnListener(this, 'ListenerHttps', {
      port: 443,
      protocol: elbv2.Protocol.HTTPS,
      loadBalancerArn: alb.ref,
      certificates: [
        { certificateArn: '[LBの証明書ARN]', },
      ],
      defaultActions: [
        { targetGroupArn: targetGroup.targetGroupArn, type: 'forward', },
      ],
    });
  }
}

5. EFSの作成

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

    const fileSystem = new efs.FileSystem(this, 'FileSystem', {
      vpc,
      fileSystemName: 'wordpress-efs',
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      securityGroup: ec2.SecurityGroup.fromSecurityGroupId(this, 'EfsSecurityGroup', cdk.Fn.importValue('EfsSecurityGroupd')),
    });
  }
}

6. System Managerのパラメータストアにパラメータ定義

Fargateのコンテナから環境変数を参照するために、System Managerのパラメータストアにパラメータを定義しておきます。

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

    new ssm.StringParameter(this, 'WpDbHostParameter', {
      description: 'Wordpress DB Host',
      parameterName: '/wordpress/db_host',
      stringValue: '[RDSのDBエンドポイント]',
    });

    new ssm.StringParameter(this, 'WpDbPortParameter', {
      description: 'Wordpress DB Port',
      parameterName: '/wordpress/db_port',
      stringValue: '[RDSのDBポート]',
    });

    new ssm.StringParameter(this, 'WpDbPasswordParameter', {
      description: 'Wordpress DB Password',
      parameterName: '/wordpress/db_password',
      stringValue: 'password',
    });

    new ssm.StringParameter(this, 'WpDbNameParameter', {
      description: 'Wordpress DB Name',
      parameterName: '/wordpress/db_name',
      stringValue: 'wordpress',
    });

    new ssm.StringParameter(this, 'WpDbUserParameter', {
      description: 'Wordpress DB User',
      parameterName: '/wordpress/db_user',
      stringValue: 'wp_admin',
    });
  }
}

7. ECSの作成

ECSのリソースを作成します。
注意すべき点は、2020年6月現在Fargateのlatestバージョンは1.3.0ですが、1.3.0ではEFSをサポートしていないため、1.4.0を指定する必要があります。
また、CDK及びCloudformationではまだ、インフラコードからFargateとEFSの連携ができないため、手で作成する必要があります。
タスク定義をインフラコードで作成した後、コンソールから「新しいリビジョン」を選択し、EFSの追加、コンテナ上でEFSをマウントします。
コンテナパスは/var/www/htmlにします。

スクリーンショット 2020-06-23 16.21.07.png
スクリーンショット 2020-06-23 16.22.14.png

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

    // Load ECS Cluster
    const cluster = new ecs.CfnCluster(this, 'Cluster', {
      clusterName: 'wordpress-cluster',
    });

    // Task Execution Role
    const executionRole = new iam.Role(this, 'TaskExecutionRole', {
      roleName: 'wordpress-taskexecution-role',
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'),
      ],
      inlinePolicies: {
        'INLINE': new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:GetParameter',
                'ssm:GetParameters',
                'ssm:GetParameterHistory',
                'ssm:GetParametersByPath',
              ],
              resources: [
                `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/*`,
              ],
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:DescribeParameters',
              ],
              resources: [
                '*',
              ],
            }),
          ],
        }),
      },
    });

    // Task Role
    const taskRole = new iam.Role(this, 'TaskRole', {
      roleName: 'wordpress-task-role',
      assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      inlinePolicies: {
        'INLINE': new iam.PolicyDocument({
          statements: [                 
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:GetParameter',
                'ssm:GetParameters',
                'ssm:GetParameterHistory',
                'ssm:GetParametersByPath',
              ],
              resources: [
                `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/*`,
              ],
            }),
            new iam.PolicyStatement({
              effect: iam.Effect.ALLOW,
              actions: [
                'ssm:DescribeParameters',
              ],
              resources: [
                '*',
              ],
            }),
          ],
        }),
      },
    });

    // Task Definition
    let taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition', {
      cpu: 256,
      executionRole,
      family: 'wordpress-taskdefinition',
      memoryLimitMiB: 512,
      taskRole,
    });

    // Container
    let containerDefinition = new ecs.ContainerDefinition(this, 'wordpress', {
      image: ecs.ContainerImage.fromRegistry('wordpress:latest'),
      taskDefinition,
      cpu: 256,
      environment: {
        REGION: cdk.Aws.REGION,
      },
      logging: ecs.LogDriver.awsLogs({
        streamPrefix: 'wordpress',
        logGroup,
      }),
      memoryReservationMiB: 512,
      secrets: {
        WORDPRESS_DB_HOST: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbHostParameterName', `/wordpress/db_host`),
        ),
        WORDPRESS_DB_PORT: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbPortParameterName', `/wordpress/db_port`),
        ),
        WORDPRESS_DB_PASSWORD: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbPasswordParameterName', `/wordpress/db_password`),
        ),
        WORDPRESS_DB_NAME: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbNameParameterName', `/wordpress/db_name`),
        ),
        WORDPRESS_DB_USER: ecs.Secret.fromSsmParameter(
          ssm.StringParameter.fromStringParameterName(this, 'WpDbUserParameterName', `/wordpress/db_user`),
        ),
      }
    });
    containerDefinition.addPortMappings({
      containerPort: 80,
      hostPort: 80,
      protocol: ecs.Protocol.TCP,
    });

    // Service
    const fargateService = new ecs.CfnService(this, 'EcsService', {
      serviceName: 'wordpress-service',
      cluster: cluster.clusterArn,
      desiredCount: 1,
      launchType: ecs.LaunchType.FARGATE,
      loadBalancers: [
        {
          containerName: 'wordpress'
          containerPort: 80,
          targetGroupArn: cdk.Fn.importValue('LbTgArn'),
        },
      ],
      deploymentConfiguration: {
        maximumPercent: 200,
        minimumHealthyPercent: 100,
      },
      networkConfiguration: {
        awsvpcConfiguration: {
          securityGroups: [
            systemSecurityGroup.securityGroupId,
          ],
          subnets: vpc.publicSubnets.map(c => c.subnetId),
          assignPublicIp: 'ENABLED',
        },
      },
      platformVersion: '1.4.0', // TODO: latest
      healthCheckGracePeriodSeconds: 30,
      taskDefinition: taskDefinition.taskDefinitionArn,
    });
  }
}

まとめ

Fargateを利用することでログの可視化(Cloudwatch)や、セキュリティ強化のためにWAFを導入したり、死活監視でXrayを使ったりなど、アーキテクチャの幅が広がりそうです。

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

AWS LambdaのCORSでハマったからreturnを共通化した

はじめに

前回、AWS Lambdaを使用したサーバーレス開発について、私がハマった箇所をまとめました。
インフラエンジニアがやってみた AWSでサーバーレス Webアプリケーション開発

そこでCORSについて毎回returnでハマるのでreturnを共通化しました。
文中のAWS LambdaのソースはPython 3.8で書かれています。

言いたいこと

CORSでエラーが出たらLambdaのreturnを疑え。

Why?

JavascriptのコンソールにCORSのエラーが出るとき、十中八九Lambdaで正しくreturnしていないからです。

CORSの仕組みや考え方は以下の参考ページが大変詳しいです。
なんとなく CORS がわかる...はもう終わりにする。

ある程度仕組みを理解して頂いたところで、CORSのエラーが出るパターンは以下になります。

  • Lambda側でOPTIONSメソッドに応答していない
  • Lambda側で応答していない or 応答している内容に不備がある

かぶっている部分もありますが、大枠でこの2つになると思います。
ポカレベルの内容ですが気づきにくく、結果ハマりました。

returnを共通化する

まず、丁寧にreturnしないといけないので、それなりにreturn部の処理が長くなってしまいます。
なので共通化してreturnできるように2つの関数を作成しました。

# API Gatewayのルールに則った成功の response を生成する
def create_success_response(body, **kwargs):
    origin  = '*'
    methods = 'GET'

    for k, v in kwargs.items():
        if k == 'origin'  : origin  = v
        if k == 'methods' : methods = v 

    headers = {
        'Access-Control-Allow-Headers' : 'Content-Type',
        'Access-Control-Allow-Origin'  : origin,
        'Access-Control-Allow-Methods' : methods
    }

    logger.info(
        'return values headers = {}, body = {}, origin = {}, methods = {}'
            .format(headers, body, origin, methods)
    )

    return {
        'isBase64Encoded': False,
        'statusCode'     : 200,
        'headers'        : headers,
        'body'           : json.dumps(body)
    }

# API Gatewayのルールに則った失敗の response を生成する
def create_error_response(body, **kwargs):
    origin  = '*'
    methods = 'GET'

    for k, v in kwargs.items():
        if k == 'origin'  : origin  = v
        if k == 'methods' : methods = v 

    headers = {
        'Access-Control-Allow-Headers' : 'Content-Type',
        'Access-Control-Allow-Origin'  : origin,
        'Access-Control-Allow-Methods' : methods
    }

    return {
        'isBase64Encoded': False,
        'statusCode': 599,
        'headers': headers,
        'body': json.dumps(body)
    }

create_error_responseのほうはstatusCode599でハードコーディングしています。
要件がある場合は、methodsと同じように引数で外部から変更できるようにしてください。
originの内容もサイトのセキュリティポリシーに合わせて、URLを絞ってください。

OPTIONSメソッドに応答していない

LambdaがOPTIONSに対してちゃんと応答しているか疑いましょう。
GET, POST, PUT, DELETE…などなどは応答していたけどOPTIONSを忘れていた、という事があると思います。
というか私がそうでした。

はい、OPTIONSについての処理を追加しましょう。

OPTIONSメソッドをちゃんと処理しよう
#=======================================================
# OPTIONS : CORSに必要
#=======================================================
elif event['httpMethod'] == 'OPTIONS':
    logger.info('handle the options method.')

    return create_success_response(
        { 'message': 'successfully: called options method.' },
        methods='GET,PATCH,DELETE'
    )

この時大事なのが、methodsにLambdaで受け付けるHTTP Methodをすべて書くことです。
PUT, PATCH, POSTなど細かな違いもちゃんと仕様に合わせて書きましょう。

応答していない or 応答している内容に不備がある

returnを書いてるから応答してる!と思ったら大間違いです!
return時にちゃんと、CORSの仕様に則った応答を返す必要があります。
具体的に言うとcreate_success_responsecreate_error_response関数のheadersの部分です。
returnを共通化したので、return時に共通化した関数を呼んであげれば大丈夫になりました。

return時に共通化した関数を呼ぶ
return create_success_response(
    response,
    methods='GET'
)

どこの処理でもreturn時に関数を呼ぶようにしています。
共通化した関数を経由することで、期待されているレスポンスに適切にフォーマットした後にreturnすることが出来るようにしました。

最後に

適切なフォーマットでreturn出来ていないことが毎回のハマりポイントでした。
returnの処理を共通化することでミスに気づきやすくなりました。

javascript consoleに出力されるCORS errorにさよなら!

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

サーバレスなVOD配信環境を構築

AWS Media Blog の Creating a secure video-on-demand (VOD) platform using AWS の記事で紹介されていた download this detailed lab guideを参考にサーバレスなVOD配信環境を構築してみました。

サーバレスなVOD配信環境でできること

認証されたユーザーのみが動画をアップロード、処理、配信および閲覧可能なサーバレスのVODストリーミングプラットフォームです。

■動画アップロード画面(管理者権限付与ユーザのみログイン可能)

ログイン

Adminログイン画面.png

動画アップロード

Adminファイルアップロード画面.png

■動画配信画面(認証済ユーザのみ)

ログイン

Guestログイン画面.png

動画一覧

Guest動画一覧画面.png

動画配信

Guest動画閲覧画面.png

サーバレスなVOD配信環境のアーキテクチャ

■概要

アーキテクチャ概要.png

■説明

DeepL翻訳
このサーバーレスVODワークフローソリューションは、
Amazon CognitoAWS AppSyncAmazon S3AWS LambdaAmazon DynamoDBAWS Secrets Manager
AWS Elemental MediaConvertAmazon CloudFrontAWS AmplifyAWS CloudFormation
を含む複数のAWSサービスを利用して構築されています。

このソリューションでは、CognitoGraphQL APIへのアクセスを保護するCognito User Poolsを介してWebアプリケーションのユーザーを認証します。
APICognito User Pool内のグループに基づいたきめ細かなアクセスが可能です。
また、Cognitoは、コンテンツをS3にアップロードするためのアクセスのために、一時的に限定された特権を持つAWSのクレデンシャルを生成しています。

GraphQL APIを利用したAppSyncでは、クライアントアプリがサーバーからデータをフェッチ、変更、サブスクライブできるようにしています。
今回のソリューションでは、GraphQL APIを利用して動画に対してCRUDCreate/Read/Update/Delete)操作を行い、動画をS3バケットに格納しています。

このワークフローでは、2つのS3バケットを使用しています。
1つは生の動画を保存するため、もう1つは異なる出力フォーマットとビットレートの動画を保存するためです。

複数の Lambda 関数は、

MediaConvert ジョブのトリガー、
DynamoDB へのメタデータの保存、
署名付き URL トークンの作成、
MediaConvert ジョブの終了時の通知の送信

など、さまざまな目的に使用されます。

MediaConvert はアップロードされた動画を 
2 Mbps
1 Mpbs
600 Kbps
400 Kbps 
 HLS 形式で自動的にトランスコードします。
ビデオコンテンツは、さまざまなネットワーク帯域幅接続を持つ複数のデバイスで再生できるように、適応ビットレートのストリームとしてエンコードされます。

Secrets Managerは、CloudFrontの秘密鍵を安全に保存し、CloudFrontURLに署名するために使用します。
CloudFrontは、実行時にアプリケーションを介してCDN Edgeのロケーションを介して、許可されたユーザーにコンテンツを安全に配信します。

その他の設定情報

Signed Cookies/URLs
CloudFrontが各URLの有効期限、許可されたIPアドレス、FileNameを設定できるようにします。
デフォルトでは、CDNで配布されたコンテンツにはNo Readアクセスが割り当てられています。
コンテンツは、アプリが生成した適切なSigned Cookieを使用してリクエストされた場合にのみダウンロードしてアクセスすることができます。
S3 CORSの設定
S3/CDN経由でコンテンツへのアクセスを許可するウェブサイトを設定します。S3 CORS設定の詳細については、こちらのブログ記事をご覧ください。

AmplifyCloudFormationは、ソリューションのバックエンドに関わるリソースやサービスの迅速な展開を容易にします。

サーバレスなVOD配信環境を構築する

■公式ドキュメント

VOD-Blog-LabGuide ※Wordファイル(docx)

■前提条件

AWSアカウント

  • IAMユーザでAWSマネジメントコンソールにログイン(step4以外)
    • IAMユーザ名:vodaws
    • IAMポリシー:AdministratorAccess
  • ルートアカウントでAWSマネジメントコンソールにログイン(step4のみ)

AWSマネジメントコンソール

  • 言語
    • 日本語
  • リージョン
    • ap-northeast-1 (Tokyo)

■Step1: Setting up Development Environme

1. AWS Cloud9コンソールに移動後、【 Create environment 】を選択

snip_20200622192939.png
※Regionが東京である事を再確認

2. Name environment の設定後、【Next step 】を選択

Name: VodAws
Description: This environment is for the AWS Cloud9 tutorial.

snip_20200622193628.png

3. Environment setting にて t3.smallを選択後、【Next step 】を選択 ※インスタンスタイプ以外はデフォルト

snip_20200622193816.png

4. 設定内容を確認後、【 Create environment 】を選択

snip_20200622194231.png

5. Welcomeタブ横の(+) を選択し、別タブの新規ターミナルを表示

snip_20200622194351.png

■Step2: Installing Packages and Configuring Environment

1. 以下のコマンドを実行してcredentialsファイルをコピー

コマンド
cp ~/.aws/credentials ~/.aws/config

snip_20200622194813.png

2.以下のコマンドを実行してAmplify CLIをインストール

コマンド
npm install -g @aws-amplify/cli

snip_20200622194905.png

インストール時にWARNが表示されますが今回は無視します。

snip_20200622195111.png

3. 以下のコマンドを実行してVodAws.zipをダウンロード

コマンド
wget https://testorlando1.s3.amazonaws.com/VodAws.zip

snip_20200622195319.png

4. 以下のコマンドを実行してVodAws.zipを解凍

コマンド
unzip VodAws.zip

snip_20200622195445.png

5. 以下のコマンドを実行してVodAwsディレクトへ移動

コマンド
cd VodAws

snip_20200622195708.png

5. 以下のコマンドを実行してNPMをインストール

コマンド
npm install

snip_20200622195847.png

インストール時にWARNが表示されますが今回は無視します。

snip_20200622200031.png

■Step3: Deploying Backend using Amplify Cli

1. 以下のコマンドを実行して~/environment/VodAwsにいることを確認

コマンド
pwd

snip_20200622200550.png

2. 以下のコマンドを実行してAWSバックエンドのセットアップ

コマンド
amplify init

snip_20200622200633.png

プロンプトの選択については下記のとおり。

プロジェクト名を入力
snip_20200622202340.png

環境を入力
snip_20200622202535.png

使用するエディタを選択
snip_20200622202558.png

対象アプリの種類を選択
snip_20200622202648.png

使用するJavaScriptフレームワークを選択
snip_20200622202715.png

プロジェクト内のソースディレクトリパスの場所を指定
snip_20200622202753.png

配布用ディレクトリパスの場所を指定
snip_20200622203006.png

ビルドコマンドを入力
snip_20200622203032.png

スタートコマンドを入力
snip_20200622203226.png

AWSプロファイルを使用
snip_20200622203248.png

defaultのAWSプロファイルを使用
snip_20200622203315.png

上記選択後、バックグラウンドで処理が走ります。

snip_20200622203431.png

3. 以下のコマンドを実行してプロジェクトにAPIモジュールを追加

コマンド
amplify add api

snip_20200622204536.png

プロンプトの選択については下記のとおり。

サービスを選択
snip_20200622204619.png

API名を入力
snip_20200622204654.png

認証形式を選択
snip_20200622204725.png

デフォルトの認証とセキュリティ設定利用有無を選択
snip_20200622204834.png

サインイン手段の選択
snip_20200622204904.png

追加設定の選択
snip_20200622204928.png

GraphQL API追加設定の選択
snip_20200622205037.png

既存GraphQLスキーマ利用有無の選択
snip_20200622205213.png

ガイド付きGraphQLスキーマ作成有無の選択
snip_20200622210023.png

最も合致する使用方法の選択
snip_20200622205304.png

スキーマ作成を今すぐ行うかどうかの選択
snip_20200622205328.png

上記選択後、APIモジュール追加処理。

snip_20200622205412.png

4. 以下のコマンドを実行してschema.graphql を編集

コマンド
cat << EOF > /home/ec2-user/environment/VodAws/amplify/backend/api/vodaws/schema.graphql

type vodAsset @model (subscriptions: {level: public})
@auth(
  rules: [
    {allow: groups, groups:["Admin"], operations: [create, update, delete, read]},
    {allow: private, operations: [read]}
  ]
)
{
  id:ID!
  title:String!
  description:String!

  #DO NOT EDIT
  video:videoObject @connection
}

#DO NOT EDIT
type videoObject @model
@auth(
  rules: [
    {allow: groups, groups:["Admin"], operations: [create, update, delete, read]},
    {allow: private, operations: [read]}
  ]
)
{
  id:ID!
  token: String @function(name: "VodAws-prod-tokenGen")
}
EOF

snip_20200622211751.png

5. 以下のコマンドを実行してデプロイを実行

コマンド
amplify push
continue?についてはYを選択

snip_20200622212509.png

プロンプトの選択については下記のとおり。

Query/Mutationのコード生成有無

snip_20200622212546.png

生成するコードの言語の選択

snip_20200622212618.png

ファイル名のパターン(queries, mutations, subscriptions)の入力

snip_20200622212700.png

全てのGraphQLの操作に対してのコード生成有無

snip_20200622212744.png

タイプ同士で相互参照している場合のネストの最大値

snip_20200622212806.png

上記選択後、デプロイ実行

snip_20200622212923.png

6. ブラウザの別タブよりCloudFormationダッシュボードへ移動

CloudFormationダッシュボードへ移動

snip_20200623010412.png

ネスト表示に切り替え

snip_20200623010551.png

7. amplify-vodaws-prod-*のスタック名を選択後、出力に表示されている値(赤丸の個所)をメモ帳等に控える

snip_20200623011219.png

8. amplify-vodaws-prod--authvodaws****のスタック名を選択後、出力に表示されている値(赤丸の個所)をメモ帳等に控える

snip_20200623012252.png

■Step4: Creating CloudFront Key Pairs

DeepL翻訳
重要
IAMユーザーはCloudFrontのキーペアを作成することができません。キーペアを作成するには、ルート資格情報を使用してログインする必要があります

1. 別ブラウザを開き、ルートアカウントでAWSマネジメントコンソールにログイン

snip_20200623102627.png

2. 画面右上のアカウント名をクリック後、【 マイセキュリティ資格情報 】を選択

snip_20200623103147.png

3. セキュリティ認証画面の【 新しいキーペアの作成 】を選択

snip_20200623103447.png

4. 【 プライベートキーファイルのダウンロード 】を選択してローカルにダウンロード

snip_20200623103606.png

DeepL翻訳
重要
CloudFrontのキーペアの秘密鍵を安全な場所に保存し、希望する管理者のみが読めるようにファイルのパーミッションを設定します。
誰かがあなたの秘密鍵を取得すると、有効な署名付きURLや署名付きクッキーを生成して、あなたのコンテンツをダウンロードすることができます。
秘密鍵は二度と取得できないので、紛失したり削除したりした場合は、新たにCloudFrontの鍵ペアを作成する必要があります

5. 出力に表示されているアクセスキーID(赤丸の個所)をメモ帳等に控える(署名付きURLや署名付きCooki作成に利用)

snip_20200623104835.png

以降はルートアカウントによる作業は発生しない為、 ルートアカウントでログインしているAWSマネジメントコンソールについてはログアウト

■Step5: Storing CloudFront Key Pairs

Step5以降はStep3にて使用したAWSマネジメントコンソール(IAMユーザログイン)を使用

1. Step3にて使用したCloud9のターミナルよりを以下のコマンドを実行してStep4の3のプライベートキーファイルをコピー

コマンド
cat /home/ec2-user/environment/privateKey.pem
※ファイルが存在しないことを確認

cat << EOF > /home/ec2-user/environment/privateKey.pem
(Step43のプライベートキーファイルの内容)
EOF

snip_20200623144840.png

■Step6: Storing Private key using AWS SecretsManager

1. 以下のコマンドを実行して秘密鍵をSecretsManagerに保存後、出力に表示されている値(赤丸の個所)をメモ帳等に控える

コマンド
aws secretsmanager create-secret --name VodAws --secret-binary file://privateKey.pem

snip_20200623143705.png

■Step 7: Verify the values of all the variables

1. 以下の変数の値を控えていることを確認

変数名
pAppClientID Step 3 の8のAppClientID
pAppClientIDWeb Step 3 の8のAppClientIDWeb
pAuthRoleArn Step 3 の7のAuthRoleArn
pIdentityPoolID Step 3の8のIdentityPoolID
pPemID Step 4 の5のアクセスID
pS3 Step 3 の7のDeploymentBucketName
pSecretPem Step 6 の1のName
pSecretPemArn Step 6 の1のARN
pSourceFolder vod-helpers (規定値)
pUnAuthRoleArn Step 3 の7のUnAuthRoleArn
pUserPoolID Step 3の8のUserPoolId
pAuthRoleName Step 3 の7のAuthRoleName
penv prod (規定値)
pUnAuthRoleName Step 3 の7のUnAuthRoleName

■Step8: Deploying CloudFormation Template

1. vod-helpers.zipをローカルにダウンロードの上、解凍(vod-helpersフォルダ)

ダウンロード先URL:https://testorlando1.s3.amazonaws.com/vod-helpers.zip

2. 別タブにてS3ダッシュボードに移動

snip_20200623115628.png

3. amplify-vodaws-prod-***-deploymentを選択

snip_20200623115825.png

4. 1のvod-helpersフォルダをアップロード画面にドラックアンドドロップ後、【アップロード】を選択

snip_20200623150256.png

5. vod-helpersを選択後、【アクション】より【公開する】を選択

snip_20200623120144.png

6. 【公開する】を選択

snip_20200623120318.png

7. Launch with CloudFormation Console のリンクを別タブで表示

Launch with CloudFormation Console
https://console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/create/review?templateURL=https:%2F%2Fs3.amazonaws.com%2Ftestorlando1%2FParentStack.json&stackName=VodAwsStack
※引数のregionをap-northeast-1に変更しています

8. スタックのクイック作成が開いたことを確認

snip_20200623120931.png

9. パラメータの空欄にStep 7の1で控えた値を入力

snip_20200623121906.png

10. 機能のチェックボックスにチェック後、【 スタックの作成 】を選択

snip_20200623122138.png

11. VodAwsStackのスタック名を選択後、出力に表示されている値(赤丸の個所)をメモ帳等に控える

snip_20200623122835.png

12. step6の1のCloud9ターミナルに戻り、以下のコマンドを実行してsrcディレクトリに移動

コマンド
cd /home/ec2-user/environment/VodAws/src/

snip_20200623123125.png

13. step6の1のCloud9ターミナルに戻り、以下のコマンドを実行してsrcディレクトリに移動

コマンド
cat << EOF > aws-video-exports.js
const awsvideoconfig = {
        "awsInputVideo": "Step8の11のoVODInputS3の値",
        "awsOutputVideo": "Step8の11のoVODOutputUrlの値"
    };

export default awsvideoconfig;
EOF

snip_20200623123611.png

13. 以下のコマンドを実行してVodAwsディレクトリに移動

コマンド
cd /home/ec2-user/environment/VodAws/

snip_20200623123820.png

■Step9: Hosting our app on Amplify

1. 以下のコマンドを実行してプロジェクトに静的ホスティングを追加

コマンド
amplify hosting add

snip_20200623131022.png

プロンプトの選択については下記のとおり。

実行するプラグインモジュールを選択

snip_20200623131154.png

環境の選択

snip_20200623131332.png

ホスティングするバケット名の確認(デフォルト)

snip_20200623131441.png

設定確認

snip_20200623131533.png

1. 以下のコマンドを実行して静的リソースをAmazon S3およびAmazon CloudFrontに公開

コマンド
amplify publish
continue?についてはYを選択

snip_20200623132037.png

2. 公開が完了するとcloudFrontのURLが表示

snip_20200623132218.png

3. 以下のコマンドを実行してnpm serverを起動

コマンド
nmp start

snip_20200623132707.png

起動確認ができたらContrl+Cで停止(今回はlocalhostの閲覧確認を行わない)

snip_20200623133012.png

4. ブラウザよりcloudFrontのURLの閲覧

snip_20200623132804.png

■Step 10: Create Users in Cognito User Pool

1. 別タブにてCognitoダッシュボードに移動

snip_20200623133258.png

2. 【 ユーザープールの管理 】を選択

snip_20200623133349.png

3. vodawsse**userpool を選択

snip_20200623133604.png

4. 【 ユーザーの作成 】を選択

snip_20200623133756.png

5. 動画公開可能のユーザ作成

今回は【Vodaws 】というユーザー名で作成
電話番号は国番号から入力 例)日本の場合(81)

snip_20200623134327.png

6. 【 グループに追加 】を選択

snip_20200623134503.png

7. Admin を入力後、【グループに追加】を選択

snip_20200623134921.png

8. ブラウザより https:// cloudFrontのURL /Admin へアクセス後、ユーザー名(Vodaws)および仮パスワードを入力の上、パスワード変更を実施

snip_20200623135118.png

snip_20200623144311.png

9. ログイン後、画像アップロード確認(mp4)

snip_20200623135429.png

snip_20200623135617.png

画像アップロード確認後、ログアウト

10. Step10の4の画面に戻り、動画閲覧専用のユーザ作成

今回は【guest】というユーザー名で作成
電話番号は国番号から入力 例)日本の場合(81)

snip_20200623140049.png

11. ブラウザより https:// cloudFrontのURL / へアクセス後、ユーザー名(guest)および仮パスワードを入力の上、パスワード変更を実施

snip_20200623140131.png

snip_20200623144131.png

12. ログイン後、Vodawsユーザーがアップロードしたsampleが表示されていることを確認

snip_20200623140253.png

snip_20200623140447.png

13. sampleを選択して動画が閲覧できることを確認

snip_20200623140920.png

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

AWSにおける最適なDB選択

DBの歴史

要件の変化(2000年~)

RDBMSだけでは様々な要件に対応できなくなり、KVSなど特性を持ったDBが誕生した。

項目 従来要件 新要件
ユーザ ~10万 100万+
データ量 ~TB PB-EB
範囲 社内 グローバル
パフォーマンス マイクロ秒

AWSにおけるデータカテゴリと選択指針

グラフ化や台帳といったカテゴリもあるが、本整理の範囲外。

データカテゴリ Relational Key-value Document In-memory
AWS サービス RDS DynamoDB DocumentDB ElastiCache
特徴 参照結合性、ACIDトランザクション 高スループット、低レイテンシー ドキュメント保存し、高速クエリ 超低レイテンシ
選択指針 汎用的なDB、特に要件がなければ… 高スケーラビリティ/数ミリ秒のレスポンスタイム/シンプルなクエリでよい スキーマを決められないデータの格納 ミリ秒未満のレスポンスタイム/キャッシュ利用したい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSにおける最適なDB選択指針

本整理の目的

特に頭を使わず、DBならRDSでしょと考えてきた基盤エンジニア(5年目)が適材適所なDB選択を体系的に学びなおしたのでまとめる。
参考は2019年のAWS Summitのセッションより。
https://www.youtube.com/watch?v=h1r8AzOdlqo

DBの歴史[要件の変化(2000年~)]

RDBMSだけでは様々な要件に対応できなくなり、KVSなど特性を持ったDBが誕生した。

項目 従来要件 新要件
ユーザ ~10万 100万+
データ量 ~TB PB-EB
範囲 社内 グローバル
パフォーマンス マイクロ秒

AWSにおけるデータカテゴリと選択指針

グラフ化や台帳といったカテゴリもあるが、本整理の範囲外。

データカテゴリ Relational Key-value Document In-memory
AWS サービス RDS DynamoDB DocumentDB ElastiCache
特徴 参照結合性、ACIDトランザクション 高スループット、低レイテンシー ドキュメント保存し、高速クエリ 超低レイテンシ
選択指針 汎用的なDB、特に要件がなければ… 高スケーラビリティ/数ミリ秒のレスポンスタイム/シンプルなクエリでよい スキーマを決められないデータの格納 ミリ秒未満のレスポンスタイム/キャッシュ利用したい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ターミナルからAWS EC2へ〜シャットダウンまで

こんにちは
今回からマークダウンも併用していこうと思います。
スライドの利点とマークダウンの利点をうまく使い分けしていきたいです。

今回も"触ってみた"系の記事なんですが、
macターミナルからAWS EC2へ入って、起動中のEC2をシャットダウンする
までの一連にトライしたので、そちらを記します。
今まで通り、自分がつまずいた箇所を明記しておきたいので、
そちらは斜体太字で記すようにしていきます。
また本記事は書籍AWSをはじめよう ~AWSによる環境構築を1から10まで~を参考にしています。

EC2サーバを立てる@AWSマネジメントコンソール

一連の作業は済んでますので、ざっくり手順ベースで振り返ります。

手順

  1. IAMユーザでAWSマネジメントコンソールにサインイン
  2. 「サービス」から「EC2」を選択
  3. 「インスタンスの作成」を押下
  4. 「Amazon Linux」を選択
  5. 「t2.micro」を選択(無料利用枠を活用するので)
  6. EBS/タグは活用しないのでスキップし、「SG」の設定
  7. 「起動」
  8. キーペアの作成(1度切り!)
  9. 「Name」からインスタンスに命名
  10. 「インスタンスの状態」が「runnning」であることを確認

上記の手順自体は恙無く進みましたが、気になっているのは6.「SG」関連です。
今回はすぐにシャットダウンするEC2インスタンスでしたので特に設定せず、
スクリーンショット 2020-06-23 11.50.32.png
上記メッセージが出てくる状態。SLA次第だとは思いますが、
具体的に
・セキュリティの詳細設定の方法
・どんなセキュリティレベルをどういうサービスに設定するのか
を判断できるようになる必要があります。今後の課題です。

あとインスタンタイプも同じく、とりあえずt系(無料枠ですし)選んでますが
なぜそのインスタンスタイプを選択するのかの判断ができるよう、別途深堀ります。

macターミナルからEC2に入る〜サーバ停止

こちらも手順ベースで。

手順

  1. macにてターミナル起動
  2. chmodで、キーペアファイルに対する読み書き制限の厳格化 chmod 600 ~/(キーペアファイル格納場所)
  3. 対象EC2インスタンスをIPアドレスで指定しつつ「-iオプション」で利用する鍵を指定 ssh ec2-user@(IPアドレス) -i ~/(キーペアファイル格納場所)
  4. 初回サーバ接続時の信頼性確認 yesを返す
  5. シャットダウンコマンドの実行 sudo shutdown -h now
  6. AWSマネジメントコンソールから、状態の「stop」を確認

スクリーンショット 2020-06-23 13.47.33.png
実際の画面がこちら。
ちょっと打鍵ミスしてやり直しましたけど手順自体は問題なく進みました。
課題になったのはコマンド慣れしていないのでsudoや-i、-hなどを調べながら実施したことです。
sudo : rootユーザに成らなくてもrootユーザの権限で処理を実行できるコマンド
オプションは調査中。その辺がわからないと自在にはできないかな。。。

 あとがき

シャットダウンまでの一連の流れ自体で(打鍵ミス以外は)詰まるところもなく、流れました。
しかし、大事なのは途中斜体太字で書いているような「なぜ」の部分だと思いますので、
その辺り、もう少し掘っていきたいと思います。

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

AWS☆☆☆☆ デプロイまでの道のり4-最終章(短いバージョン、全4回)

1)背景

第4回目の最終章です。
自身のポートフォリオをデプロイしました。最後の作業としてドメインの紐つけをします。
AWS関連手順記事はすごく多いので、ここでは備忘録も含めて、非常に端的に手順を記載します。

2)環境

項目 内容
OS.Amazon Linux AMI release 2018.03
Ruby v2.5.1
Ruby On Rails v5.2.4.3
MySQL v5.6
Unicorn v5.4.1

3)内容

以下設定で65分程度かなと思います。(段取りが分かっていれば、30分)
※【ローカルマシン】指定以外は、全てAWSでの作業になります。

(1)お名前ドットコムからドメインを取得(20分)

同サイトからドメインを契約します。ここについては割愛いたします。

(2)Nginxの導入と設定(20分)

  • yumパッケージを活用してNginx(webサーバ)を導入する
  • Nginxのconfigファイルを設定する
  • Nginxのパッケージ権限の付与(/var/lib/nginx)
  • Unicorn設定ファイルの変更(Nginxを経由して処理を行うために設定)

(3)AWS-Route53の設定(10分)

  • ホストゾーンの新規作成
  • 新規レコードセットに同ElaspicIPを登録する

(4)お名前ドットコムの設定(15分)

  • AWSで新規に割り当てられたNSレコードをネームサーバ名として登録する
  • DNS設定(ElaspicIPと、ドメインの紐付けを行う)

(2)Nginx設定2(10分)

  • 登録したドメイン名を同設定ファイルに設定する。

これで完了です。
指定のドメインにアクセスすると、NGINXを介してUnicornが起動しているrailsアプリを参照することが出来ます。
正直簡単です。出来るだけ難しく書かないように端的に記載しました。
以上です。

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

AWS 基礎

前提

AWS(クラウドプラットフォーム)について学んだことを書いていきます。

本題

巨大クラウドプラットフォーム

では、クラウドサービスの次にAWSついて書いていきます。

世界15箇所で提供されるクラウドサービス

展開されている地域のことをリージョンと言います。
AWSの利用者は、これらの好きなリージョンに、仮想サーバーや仮想ネットワークなどを構築して運用できます。
居住国とリージョンとは関係ありません。
日本国内に住んでいても、バージニア北部などの米国のリージョンを使うこともできます。
とはいえ、地理的に近いリージョンほどネットワークのレスポンスが速いため、日本国内から利用する場合には、東京リージョン(ap-northeast-1)を使うことがほとんどです。

東京リージョン以外を使う理由
・東京リージョンでは、まだ提供されていないサービスを使いたい
・東京リージョンよりも、安く運用したい
・東京リージョンが万が一異常を起こした時のバックアップとして、別のリージョンに設置したい

AWSでは100以上のサービスが提供されている

AWSでは特にマネージドサービスが充実しています。

AWSでよく使われるサービス

無題のスプレッドシート (5)_page-0001.jpg

基本的にこれだけ理解すれば、オンプレミスで実現していたのと同じ、ほとんどの業務システムを構築できるはずです。

データはリージョン間を勝手に移動しない
AWSで仮想サーバーなどを構築するときには、どのリージョンに作成するのかを初めに決めます。
リージョンを1度決めると、そのサーバーが他のリージョンに勝手に移動することはありません。
そのため、国外に持ち出してはいけないデータを運用する場合も、東京リージョン上のサーバーで運用すれば問題ありません。

AWSのデメリット

主なデメリットは次の2つですが運用の工夫で解決できます。

1、定額での運用ができない
AWSは従量課金であるため、定額での運用ができない点が運用上のデメリットとしてよく挙げられます。
情報システムでは予算を確保して運用することも多いため、AWSを使いたいと思っても「いくらかかるかわからない」のでは、社内決裁が降りない、取引先の理解が得られないことも多いでしょう。
しかし、AWSにはAWS簡易見積もりツールが提供されており、高い精度で、どのぐらいの金額がかかるかを予測できます。
予測しにくいのが、「ネットワークトラフィック」ですが、「1ユーザー当たり、平均でどのくらいのデータを送受信するか」を実測し、それに想定されるユーザー数を掛け算すれば、想定から大きくかけ離れた値になることはありません。

2、自分で全てをコントロールできなくなる
そして次によくある問題が、AWSに運用管理を任せるので、完全に自分でコントロールできなくなってしまうという点です。
これは、主に次の3つ問題(不安)に分けることができます。

・データ流出の問題
まず、AWSにデータを預けることになるので、データの消失や流失が起きないかが心配になります。
しかし、AWSでは各種プライバシー認証・コンプライアンス認証をとった環境で運営されており、設定のミスや各種攻撃などに注意すればAWSを使っただけでデータが筒抜けということはまず起こりません。
もちろん、オンプレミス同様にアクセス情報を秘匿するといった対策は必須です。

・AWSの大規模障害
次に危惧されるのがAWS自体の大規模障害です。
社内の基幹システムをAWSで運用する場合、万一、AWSが停止した時は、社内業務が停止する恐れがあります。
オンプレミスでの運用ならエンジニアを派遣して、ひとまず暫定復旧を始めることまでできますが、AWSの場合は、AWSに任せて復旧を待つよりほか方法がありません。
もし本当に停止すると業務を打撃するようなシステムの場合は、AWSを利用しない、もしくは、AWSとオンプレミスを併用して、AWSが止まったときにはオンプレミスに切り替えられるようにするなど対策が必要です。
可用性や今までの障害情報などを適切に収集し、AWSを通信しない体制を構築しておくことは必要です。

・サービスがいつまで提供されるか
最後に、いつまで(個別の、あるいは全体の)サービスが提供されるのかという問題もあります。
もし、サービスが終わってしまうと、別のシステムに作り替えなければいけません。
こうした懸念を避けるには、多数のユーザーが使っているサービスだけで構成するあまりAWS独自のアンマネージドサービスに依存した作りにしないといった設計上の考慮が大事です。

AWSを過信しない
AWSは管理運用コストを減らせる、サービスを迅速に増強できるなど、数多くのメリットがあります。
これらの面ではオンプレミスのサービスの利便性を完全に上回ります。
可用性やセキュリティについての懸念もありますが、AWSではこれはかなり高水準で保たれています。
利用企業、団体のリストを見れば信頼性の高さがうかがえます。
コスト面でも、いくつかのケースではオンプレミスよりも優れているでしょう。
初期費用をかけられないようなケースでは、設備投資不要で必要な時だけ費用が発生するAWSは強い見方となります。
しかし、過信は禁物です。
クラウドサービスだからサービスが安定する、安全になる、安価になる訳ではありません。

IAMユーザー

サインインしているAWSアカウント(ルートユーザー)は、AWSに対する全ての操作のほか、支払い方法の変更や解約など、契約に関する操作もできる強力なアカウントです。
万が一、漏洩すると被害が大きくなります。
そこで、AWSアカウントは、そうした特殊な操作が必要な時にだけにしか使わないようにし、普段のAWS操作には、別アカウントを作成して、そのユーザーを使って操作するようにします。
AWSを操作できるユーザーのことをIAMユーザーと言います。

アクションとポリシー

AWSにおける各種操作は、アクションと呼ばれる単位で構成されています。
例えば、仮想サーバーを作成できるアクション、仮想サーバーを実行できるアクション、ストレージを読み込みできるアクション、ストレージを読み書きできるアクションなどです。
アクションは、とても数が多く、一つ一つ設定すると煩雑なばかりか、設定漏れも生じやすくなります。
そこでAWSでは、一つ一つ権限を設定するのではなく、あらかじめ目的ごとにアクションをグループ化したポリシーと呼ばれるものをIAMユーザーに適用することで、ユーザーが操作可能な権限を付与します。
ポリシーは自分で作ることもできますが、既存のポリシーがたくさん用意されています。
例えば、仮想サーバーに関する全ての操作ができるポリシー、仮想サーバーの新規作成や削除、設定変更などはできないが、実行や停止はできるポリシー、ストレージに関する全ての操作ができるポリシー、ストレージに関する読み書きができるポリシーなどです。
IAMユーザーに対して、このしたポリシーを複数適用すると、それらのポリシーで許可されているアクションが操作できるようになります。

・IAMグループ
IAMグループは、IAMユーザーをグループ化して権限設定する概念です。
複数のユーザーに対して、同じ権限設定をしたいときに使います。

・IAMロール
IAMユーザーがユーザー(人間)に対するアカウントなのに対し、IAMロールはAWSのリソースに対するアカウントです。
例えば、仮想サーバー(EC2)から、ストレージやサービス(S3)にアクセスする場合、IAMユーザーで管理しようとすると、EC2のプログラムに、S3にアクセスするためのユーザー名やパスワード(より正確に言うとIAMの設定で作成できるアクセスキーやシークレットアクセスキー)を使って認証する必要があります。
しかし、AWSではそうする代わりにS3にアクセスできるIAMロールを作成しておき、そのIAMロールをEC2に適用すると、それだけでアクセスできるようになります。
このように、あるサービスから、別のサービスにアクセスしたい時、アクセス元のサービスに対して設定して使うのが、IAMロールです。

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

QRコード読み取り(ZBar)

ZBar bar code reader
を使う。

まずはインストール

OS「Amazon Linux AMI」の場合
yum install -y zbar-tools

OS「Linux version XXX.amzn1.x86_64」の場合(2014年から使っているEC2)
yum install zbar-devel
pip install zbar
or
/usr/local/bin/pip2.7 install zbar
※Python3系だと動かないので2.6or2.7でインストール
Python3系の場合は↓これを入れると出来るらしい.
https://github.com/zplab/zbar-py

実行
zbarimg qr.png
QR-Code:http://aaaaa/ddddd.html

urlもいける便利
zbarimg https://wwww.sample.com/qr.png
QR-Code:http://aaaaa/ddddd.html

時間がないので後日記事修正

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

【22時間で合格】 SAA-C02 AWS認定ソリューションアーキテクト-アソシエイト合格NRTA【多分これが一番早いと思います】

概要

SAA-C02受験、はーじまーるよー。

というわけで昨日2020/06/22、SAA-C02版のAWS認定ソリューションアーキテクト-アソシエイトに合格したので、私がどんな勉強をしたのか、どんな問題が出たのかなどをお伝えできればと思います。

前提として、私は2018年末にクラウドエンジニアとなってから、AWSのアーキテクチャなどについてはあまり触っていません。CLIでいくつかのサービスを自動的に設定するような実装を何度かしたことがある程度で、基本的には業務の自動化をメイン業務としていました。また、昨年3月にクラウドプラクティショナーを取得しています。

今回は有料アイテムを使ったコストパフォーマンスチャートを使用します。
途中ガバって時間を割いてしまったところがありますが、実際にはやらないほうがいいと思います。

試験概要

レギュレーション

・勉強した時間と試験時間のみ計測を行います。
・その他日常生活時間は含みません。(Not Real Time Attack)
・今回は縛りプレイではないので、有料アイテムを使用します。
・前作のクリア報酬を使用します。
・タイムセールを使用します。

攻略チャート

勉強内容

1.黒本

リンク 3h 2618円
3時間くらいでとりあえず一通り読んで章末問題を回答しました。
古い本なので試験に直接役立つことはなかったですが、おおざっぱにベースとなる知識を仕入れられるという意味ではよかったかもしれません。
付属の模擬テストは面倒だったのでやりませんでした。

2.ウェルアーキテクトフレームワーク

リンク 1h
本日のガバプレイ枠です。
しばらく読んで資格の役に立たなそうだったので読むのをやめました。
実際に実務で構築する際は何をしなければならないかのチェックリストになりそうな内容なのでよいと思いますが、この内容は踏み込みすぎという印象を受けました。

3.模擬試験(udemy)

リンク 16h 1610円
1回分につき回答40min-1h、復讐1hくらいのペースで1回ずつ解きました。
また、そこで得た内容を整理するのに2hくらいかけました。
以下はその結果です。

初回 ①(基礎知識)の正答率72%
初回 ⑥(高難易度)の正答率40% 
初回 ②(高難易度)の正答率56%
初回 ③(高難易度)の正答率66%
初回 ④(高難易度)の正答率56%
初回 ⑤(高難易度)の正答率52%(当日の朝解いた)

何週もするのは問題を暗記する効果しかないと思うので、1回ずつだけやりました。
普通に難しくて、試験前はめちゃくちゃ不安になりました。
とはいえこの模擬テストは新版対応をうたってるだけあって試験対策としてとても良いと思います。
ここに出てくる問題でわからなかったものを調べるのを繰り返していれば、本試験でも十分な知識が身についているはずです。
本試験はこの内容の半分かもうちょいくらいの難易度だと思っていいです。
めっちゃ簡単に感じました。

4.AWS Solutions Architect - Associate Practice

40min 2000円→無料(クラウドプラクティショナー合格特典)
本日のガバプレイ枠その2です。
AWSが用意した公式模擬問題25問くらい
試験前日に解いたら結果が届いたのは本試験結果と同時だったので正直そんなに意味はなかったです。
Udemyの模擬問題のほうが安くて量が多くて解説もあって答え合わせもしやすくてよいので割と無駄です。
無料ならとりあえずやってみてもいいかな。

試験

使用した時間 1h20min 15000円→7500円(クラウドプラクティショナー合格特典)
テストセンターで受験しました。
現在は自宅で受験も可能なようですが、英語でのやり取りが必要なのと壁にポスターはってあったりするだけでもダメなようなので、やめました。
実はコロナの影響で腰痛がやばくて毎週注射三本うってるんですが、テストセンターでは問題より痛みと戦うほうがつらかったです。
めちゃくちゃ慎重にといても試験時間の半分くらいで終わります。
Udemyの模擬試験やってると、サービス問題が多いなと感じるはずです。
また、出題傾向も近いですし、全く同じような内容が試験として何問かありました。

内容としては「こういう構成と背景があるんだけど」という前提のもと、「こういう状況で何を使う?」「どういう組み合わせでこれを実現する?」「このポリシー読める?」「サービスの特徴は?」みたいなことを聞かれまくります。
Udemyとの難易度の差は、サービスの概要くらいを知っていれば消せる選択肢が多くあって消去法でも解ける問題が多いことと、必要とされる理解度がもう少し浅くなることでしょうか。

クリアにかかったコスト

時間 : 22h
お金 :11728円(特典なしの場合:21228円)

おわりに

総勉強時間、コスト共にかなり抑えることができました。
時間だけでもこれの4倍くらいかけるかたも多いのではないでしょうか。
実際にはほぼUdemyの模擬試験だけで受かるという方も多い気がします。
多分これが一番早いと思います。

アソシエイト取ろうとするときに最初どんな勉強方法でやるかって皆さん悩まれると思うんですけど、や「ウェルアーキテクト」だの「黒本青本アクセンチュア本」だの「主要サービスのよくある質問」だの「ハンズオンコンテンツ」だの「ホワイトペーパー」だの「無料トレーニング」だの「有料トレーニング」だのといろいろ方法はあるし、特にサービスごとにブラックベルト読めみたいな話はよくあると思います。

私はブラックベルトちゃんと読んだことないのでいいのかわかりませんが、AWSよくわからんしサービス使ってみてるわけでもない人がただでさえ出題範囲が広いサービスのさらに個別の詳細資料読めっていわれるといろんな概念知らないと理解しにくく難しく、「めんどくさいなー」と思うんじゃないかと思います。というか僕はブラックベルト読んだほうがいいよと助言をいただいたのですが、面倒だなぁと思ってやりませんでした。

安心してください。ブラックベルト読まなくても受かります。

以下、完走した感想ですが、勉強してる間は「こんなにさぼっていいんだろうか」と思いながらやってました。実際最初は上にあげたようないろんな資料を参照してめっちゃ勉強しなきゃと思っていたのですが、業務以外にもAWSエンジェル道場に参加していることもあって時間があまりなく、途中で効率重視に切り替えました。正直やってよかったと思いますが、あくまでそれは実務でAWSを触る前提の話です。もし受験される方が実務経験がない方なら、単純な試験対策は仕事になった時に苦労すると思います。この場合、ハンズオンで触りながらゆっくりやるといいと思います。

以上、ありがとうございました。

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

[AWS]VPCの設計の考え方

VPCとは

・Virtual Private Cloudの略で、AWSアカウントを作成するとデフォルトで作成される。
・ネットワーク的に分離された領域。インターネットGWをアタッチしないと外と通信もできない。
・FWはセキュリティグループと呼ばれるもので制御する。
・VPCの中にサブネットを作成し、セグメント単位での管理が可能
・ルートテーブルでルーティング制御ができる

シングル?マルチ?

一つのアカウントで2つ以上のVPCを作ったり、複数アカウントで複数のVPCを作るというケースもあります。
まず、シングルのVPCの場合、全ての環境が一つの空間に作られます。
マルチでVPCを作成する場合は、本番環境、ステージング環境でVPCを分けるパターンがあります。

VPCの設計はアカウント設計に関連します。
例えば、アカウントをシステムごとに分ける、であれば
Aシステムのアカウントに、本番/ステージング/開発でVPCを分けるケースが考えられます。
他にも、アカウントを環境ごとに変える、であれば
本番環境のアカウントには、AシステムのVPC、BシステムのVPCという分け方も考えられます。

VPCを分けることのメリデメ

一番大きいのは、リスクを分断できるという点です。
後述しますが、一つのVPCに本番や検証環境が混在していると、誤操作のリスクもありますし
何かしらの攻撃で侵入を許した場合、同一VPCは影響を受けてしまいます。
なので、セキュリティの面からも環境毎にネットワークを分離することをオススメします。

逆にデメリットですが、VPCが増えすぎると管理がしづらいというのがあります。
VPCピアリングの設定が複雑になってきます。
ですが今はTransitGatewayというものがあるので、そちらを活用すればそのデメリットは軽減されます。

VPCの設計注意点

IPアドレスの範囲に気をつける

VPCを作成する際に使用するIPの範囲は一般的には以下を指定します。
10.0.0.0 - 10.255.255.255
172.16.0.0 - 172.31.255.255
192.168.0.0 - 192.168.255.255

気をつけないといけないのは、VPCピアリングなど別のVPCと繋ぐ場合にIPアドレスの範囲が重複していると対応できません

よくある構成として、環境毎、あるいはシステム・環境毎にアカウント、VPCを分ける場合
以下が参考になりましたので、リンクを貼っておきます。
AWS導入前に知っておきたかった「VPC設計」

VPCピアリングの特徴

異なるVCP同士を繋げるVPCピアリングという方法があります。
これは以下の注意点があります。
・IPアドレスの重複がないこと
・同じリージョンであること
・二つ先のVPCには直接通信ができない。
 →VPCがA,B,Cとあった場合でVPCピアリングをAとB、BとCとしている場合、
  AとCは直接通信ができない、という制約があります。
なので、社内でVPCが増えていくようなアカウント・VPC設計をしているのであれば
VPCピアリングを使用するよりも、TransitGatewayで集約した方がいいです。

これだけは最低限考慮する

昔は1アカウント1VPCなんていう考えもあったのかもしれませんが、以下の観点では最低限分けた方がいいです。
・環境(本番、ステージング、開発)でVPCを分ける
 →いったん攻撃を受けてしまったら、同一VPCは影響を受ける可能性が非常に高い
・レイヤー(WEB、DB、フロント(LB))はサブネットで分ける
 →インターネットとの接続点は明確に最小限に分けておく

最後に

AWS案件に携わってまだ1ヶ月のレベルなので、指摘や意見などあればどんどんコメントいただけるとありがたいです。

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

(Mac)AWS Cloud9テキストエディタのショートカットキー

controlキー(^)主体

ショートカットキー 意味
^ + w テキスト内のコードを
折り返して表示する。
^ + t カーソルの左の文字を
右へズラす。
^ + @ インデントを左へ
(shift+tab)と同じ
^ + g 単語を選択、
2回目以降は他の
同じ単語を選択する
^ + a 行の先頭へカーソルを移動
^ + u 単語を大文字に変換
^ + o 行を挿入
^ + ] インデントを右へ
^ + v 一番下の行へ
カーソルを移動
^ + n 新規のテキストエディタを開く

optionキー(⌥)主体

ショートカットキー 意味
⌥ + t 新規ターミナルを開く
⌥ + s ターミナルから
テキストエディタに
カーソルをとばす

他のキーは記号を打ち込める。


commandキー(⌘)主体

ショートカットキー 意味
⌘ + _ エディタ画面全体を縮小
⌘ + m ウィンドウを最小化する
⌘ + ] オープンファイルを
切り替える
(右のファイルへ)
⌘ + ] オープンファイルを
切り替える
(左のファイルへ)
⌘ + > ショートカットキー
コマンド一覧を表示
⌘ + < setting画面を表示
⌘ + o ファイル一覧を表示
⌘ + i オープンファイル一覧を表示
⌘ + u treeビューの表示・非表示
⌘ + a テキストエディタ上で
全てを選択

他にもわかったことがあれば追記します。

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

クラウド認定資格について整理する (AWS / Azure / GCP)

私の所属する会社の部門では、クラウド認定資格を取得しよう、というムードが高まっています。クラウド認定資格について調べる担当となったので、大手3クラウドについて認定資格を調べてみました。なお金額はざっくり円換算しています。2020年6月時点でのまとめとなります。

AWS

まずは王道のAWS認定資格となります。 12個の資格があります。AWS Certifications というようです。

まとめ

  • 入門編 (約1.1万円/90分):
    • Cloud Practitioner
  • 一般編 (約1.5万円/130分):
    • Solutions Architect Associate
    • Developer Associate
    • DevOps Administrator Associate
  • 応用編 (約3万円/180分):
    • Solutions Architect Professional
    • DevOps Engineer Professional
  • 専門編 (約3万円/180分):
    • Advanced Networking Specialty
    • Security Specialty
    • Machine Learning Specialty
    • Alexa Skill Builder Specialty
    • Data Analytics Specialty
    • Database Specialty

参考

AWS 認定
https://aws.amazon.com/jp/certification/

Azure

次にAzure認定資格です。少しややこしいのですが試験に合格しないと認定資格を受験できないようです。MCP (Microsoft Certification Program) というようです。5つに整理されたようなのですが、ロールごとにもっと細かく試験は準備されているようです(パブリッククラウド認定資格まとめ に整理されています)。

まとめ

  • 入門編 (約1.25万円/90分):
    • Azure Fundamentals (AZ-900)
  • 一般編 (約2.1万円/180分〜210分):
    • Azure Administrator Associate (AZ-104)
    • Azure Developer Associate (AZ-204)
  • 応用編 (約2.1万円/210分):
    • Azure DevOps Engineer Expert (AZ-400)
    • Azure Solutions Architect Expert (AZ-300, AZ-301)
  • 専門編 (約2.1万円/210分):
    • -

参考

【Azureの資格2019年版】Azureの認定資格とクラウド初心者におすすめの資格をご紹介
https://www.fenet.jp/infla/column/technology/66/
Azure関連のMCP試験と認定資格 ~2019年版~
https://blog.nextscape.net/archives/Date/2018/12/mcp2019
パブリッククラウド認定資格まとめ AWS / Microsoft Azure / Google Cloud
https://qiita.com/nakazax/items/47a22b3ca3280a0f9891

GCP

最後に、GCPです。だいたいAWS、Azureと同じような認定があるようです。7つありました。言語ごとに個別の Google Cloud Webassessor アカウントが必要とのことです。Google Cloud Certified と言うのが共通の呼び名?のようです。

まとめ

  • 入門編:
    • -
  • 一般編 (約1.6万円/120分):
    • Associate Cloud Engineer (JPN)
  • 応用編 (約2.1万円/120分):
    • Professional Cloud Architect (JPN)
    • Professional Data Engineer (JPN)
    • Professional Cloud DevOps Engineer
    • Professional Cloud Network Engineer
    • Professional Cloud Security Engineer
    • Professional Collaboration Engineer (JPN)
  • 専門編:
    • -

参考

認定資格取得のための登録
https://cloud.google.com/certification/register?hl=ja

それでは全員全冠目指してがんばるぞ。

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

AWS CodeBuild で Jenkins では成功していたファイルディレクトリの権限系のテストをするとなんかうまくいかない

この記事を書くに至った経緯

私が開発しているチームでは、Jenkinsでビルド・テストを行っていました。
色々と環境をAWSに載せ替えていく中で、AWS CodeBuildを使用することになりました。
ところが、ReadOnlyに設定したファイルにWriteができてしまうではないですか!
これはどうして、ということで調べた結果、わかったことがあります。

何が原因だった?どうやって解決した?

CodeBuild内ではデフォルトではroot権限で実行されます。公式のドキュメントにそう記載されています。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/troubleshooting.html#troubleshooting-root-build-commands

今までビルド・テストを行っていたのはオンプレCIサーバ上のJenkinsです。
JenkinsではJenkinsユーザでビルドコマンド等が実行されていました。

じゃあnon-rootなユーザをadduserしてそのユーザで実行してあげればいいかな、とも思ったのですが、
CodeBuildのDockerFileを読むとcodebuild-userなるユーザがいることに気がつきました。

https://github.com/aws/aws-codebuild-docker-images/blob/master/al2/x86_64/standard/2.0/Dockerfile
こちらの42行目にRUN useradd codebuild-userと書かれています。

じゃあ、それを利用してあげればいいんじゃない?ってことで、やってあげると無事に成功しました。

教訓

  • ビルドやテストを行っているユーザがどういう権限を持っているのかを把握するのは大切
  • 公式のDockerImage(というか公式の何かしら)に書かれていることを少しでも把握することは問題解決の一歩となる
    • 今回は答えだったけど
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CodeBuild で オンプレのJenkins では成功していたファイルディレクトリの権限系のテストをするとなんかうまくいかない

この記事を書くに至った経緯

私が開発しているチームでは、Jenkinsでビルド・テストを行っていました。
色々と環境をAWSに載せ替えていく中で、AWS CodeBuildを使用することになりました。
ところが、ReadOnlyに設定したファイルにWriteできないことをテストすると失敗しているではないか!
これはどうして、ということで調べた結果、わかったことがあります。

何が原因だった?どうやって解決した?

CodeBuild内ではデフォルトでroot権限で実行されます。公式のドキュメントにそう記載されています。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/troubleshooting.html#troubleshooting-root-build-commands

今までビルド・テストを行っていたのはオンプレCIサーバ上のJenkinsです。
JenkinsではJenkinsユーザでビルドコマンド等が実行されていました。

じゃあnon-rootなユーザをadduserしてそのユーザで実行してあげればいいかな、とも思ったのですが、
CodeBuildのDockerFileを読むとcodebuild-userなるユーザがいることに気がつきました。

https://github.com/aws/aws-codebuild-docker-images/blob/master/al2/x86_64/standard/2.0/Dockerfile
こちらの42行目にRUN useradd codebuild-userと書かれています。

じゃあ、それを利用してあげればいいんじゃない?ってことで、やってあげると無事に成功しました。

教訓

  • ビルドやテストを行っているユーザがどういう権限を持っているのかを把握するのは大切
  • 公式のDockerImage(というか公式の何かしら)に書かれていることを少しでも把握することは問題解決の一歩となる
    • 今回は答えだったけど
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CodeBuild において オンプレのJenkins では成功していたファイル権限系のテストをするとうまくいかない

この記事を書くに至った経緯

私が開発しているチームでは、Jenkinsでビルド・テストを行っていました。
色々と環境をAWSに載せ替えていく中で、AWS CodeBuildを使用することになりました。
ところが、ReadOnlyに設定したファイルにWriteできないことをテストすると失敗しているではないか!
これはどうして、ということで調べた結果、わかったことがあります。

何が原因だった?どうやって解決した?

CodeBuild内ではデフォルトでroot権限で実行されます。公式のドキュメントにそう記載されています。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/troubleshooting.html#troubleshooting-root-build-commands

今までビルド・テストを行っていたのはオンプレCIサーバ上のJenkinsです。
JenkinsではJenkinsユーザでビルドコマンド等が実行されていました。

じゃあnon-rootなユーザをadduserしてそのユーザで実行してあげればいいかな、とも思ったのですが、
CodeBuildのDockerFileを読むとcodebuild-userなるユーザがいることに気がつきました。

https://github.com/aws/aws-codebuild-docker-images/blob/master/al2/x86_64/standard/2.0/Dockerfile
こちらの42行目にRUN useradd codebuild-userと書かれています。

じゃあ、それを利用してあげればいいんじゃない?ってことで、やってあげると無事に成功しました。

教訓

  • ビルドやテストを行っているユーザがどういう権限を持っているのかを把握するのは大切
  • 公式のDockerImage(というか公式の何かしら)に書かれていることを少しでも把握することは問題解決の一歩となる
    • 今回は答えだったけど
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む