BUNSEN

個人的な学習ログ

AWS Amplifyを使用した爆速バックエンド構築[認証/Auth編]

概要

この記事は以下の連続した記事の2本目です。 細かい説明や背景などは以前の記事を参照してください。

  1. AWS Amplifyを使用した爆速バックエンド構築[ホスティング/Hosting編]

この記事では以前作成したReactアプリケーションに認証機能を追加してきます。

認証機能について

Amplifyでは認証機能をCognitoを使用して提供しています。

詳細はCognitoの公式を調べていただきたいですがCognitoでは様々な機能が搭載されています。 今回はその中でもAmplifyの用意したデフォルトの最低限の認証機能を使用していきます。

  • IAMによる認可機能
  • MFA機能OFF
  • パスワードポリシー
    • 最低8文字
    • 必須文字種なし
  • 登録に必要なパラメータ
    • Eメールアドレス
  • ユーザ名でログイン
  • 登録時にメールアドレスの確認

認証機能構築

$ amplify add auth
Using service: Cognito, provided by: awscloudformation
 
 The current configured provider is Amazon Cognito. 
 
 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Username
 Do you want to configure advanced settings? Yes, I want to make some additional changes.
 Warning: you will not be able to edit these selections. 
 What attributes are required for signing up? Email
 Do you want to enable any of the following capabilities? 
Successfully added auth resource reacttutorial1b2d47db locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

    Current Environment: dev
    
┌──────────┬───────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name         │ Operation │ Provider plugin   │
├──────────┼───────────────────────┼───────────┼───────────────────┤
│ Auth     │ reacttutorial1b2d47db │ Create    │ awscloudformation │
└──────────┴───────────────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes
⠇ Updating resources in the cloud. This may take a few minutes...

~~ 略 ~~

✔ All resources are updated in the cloud
 

上記手順を実行するとCognitoコンソールのユーザプールからユーザプールが作成されていることが確認できます。

細かいパラメータの変更が必要な場合は一つ目の対話で Default configuration以外を選択することで可能になります。

フロントエンド実装

以下の機能を実装しました。

  • 未ログインの場合、ログイン画面を表示
  • 必須項目の入力画面を備えたサインアップ画面
  • ログイン後にゲーム画面の表示
  • ゲーム画面にサインアウトボタン
  • ゲーム画面にユーザ情報表示

各種画面は @aws-amplify/ui-react の物を使用しています。 最終的なコードはここ (GitHubリポジトリ)を参照してください。 公式サイトを参照して実装しています。

動作確認

構築した資材をmasterブランチにマージし、AmplifyコンソールからCI/CDが修了したことを確認して動作確認を行います。

  1. フロントにアクセスし、ログイン画面を確認する。

    f:id:ahyt910:20210913174143p:plain
    AmplifySignIn

  2. 新規登録へのリンクをクリックし、サインアップ画面を確認。

    f:id:ahyt910:20210913174240p:plain
    AmplifySignUp

  3. サインアップし、メールアドレス宛に認証コードが届くことを確認。

    f:id:ahyt910:20210913174315p:plain
    AmplifyCofirmSignUp

  4. Game画面が表示され、自身のユーザ名が表示されていることを確認。

    f:id:ahyt910:20210913174401p:plain
    AmplifyHome

  5. ログアウト/ログインができることを確認。

  6. Cognitoにユーザが追加されていることを確認。

    f:id:ahyt910:20210913174456p:plain
    CognitoUserPool

最後に

無事に、認証機能をReactチュートリアルに追加することができました!

ただ、Amplifyは簡単に構築できたのですが、Reactはかなり苦戦してしまいました。。。

次回はAPIを使用したGameの進行状況の保存を試してみたいと思います。

AWS Amplifyを使用した爆速バックエンド構築[ホスティング/Hosting編]

背景

AWS Amplifyというサービスに業務で触れる機会があったのですが、こんな便利なサービスが存在したのかと衝撃を受けてしまいました。。。 これまでサーバを立ててサービスをインストールして最新を保つように運用をしてきたのですが、それらをマネージドで提供してくれる。しかも環境面の考慮まで、、、今後何らかのサービス開発をする際は積極的に使っていこうと思いましたのでここにメモを残したいと思います。

前提

  • webアプリケーションを構築する前提で話を進めますがバックエンドはそのままネイティブアプリにも流用/併用可能です。
  • webアプリケーションはnodejs + reactで開発します。
  • amplify cli のインストールは公式手順をご参照ください。
  • AWSアカウントにおいてamplify関連のIAMポリシーを所持しているものとします。
  • webフロントについては詳しくない為、実装の解説は最低限とします。

環境

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
$ npm --version
6.14.15
$ node --version
v14.17.6
$ amplify --version
5.4.0

概要

react tutorialで作成したwebアプリケーションをクラウド上にamplifyを使用してホスティングする。

web application作成

Amplify CLIの準備

$ amplify configure
  • 上記コマンドでは~/.aws/config~/.aws/credentialsの設定を行っている。つまり、すでにAWS CLIなどで認証情報を設定している場合は実行する必要はない。
  • ただし、~/.aws/configのProfile Nameに紐づくリージョンで作成されるため、実行権限を持つアカウントかつAmplifyのリージョンがデフォルトリージョンと同一であるプロフィールを作成しておく必要がある。

Amplify Applicationの作成

$ pwd
/home/ec2-user/react-tutorial
$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project reacttutorial
The following configuration will be applied:

Project information
| Name: reacttutorial
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start

? Initialize the project with the above configuration? Yes
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html

? Please choose the profile you want to use default

~~ 略 ~~

✔ Initialized provider successfully.
Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify add <category>" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify console" to open the Amplify Console and view your project status
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything

主にプロジェクトやローカル開発環境、AWSアクセス情報について聞かれるので対話式で入力する。

上記のログが出力されるとAmplifyアプリケーションの作成が完了する。

Amplifyにより提供されるHosting機能について

現状Amplifyでは主に以下の2種類のHosting機能の作成が可能

  1. Hosting with Amplify Console
  2. Amazon CloudFront and S3

それぞれにメリット/デメリットある。

  • Amplify Console
    • メリット
      • AmplifyConsole経由の容易なカスタムドメインの設定
      • コミットベースのCI/CD機能
    • デメリット
      • ホスティングしている実リソース(CloudFront, S3と思われる)がコンソールから見えず細かな設定が不可能
  • CloudFront + S3
    • メリット
      • CloudFrontやS3に対し細かな設定をすることができる
    • デメリット
      • CI/CDが必要な場合自身でその機能を用意する必要がある

柔軟なカスタマイズが必要な場合やAmplifyデフォルトの性能で満足できない場合はCloudFront + S3を選択する価値があると思われる。主にAWS WAFをアタッチする必要性がある場合などが考えられる。

今回はHosting with Amplify Consoleを選択する。

Hosting機能の実装

以降はAmplifyコンソールでの操作となる。 以下の前提で手順を説明する。 - アプリケーションコードはGitHubで管理されている。

  1. Amplifyコンソールから作成したアプリケーションを選択。
  2. Frontend environmentsタブを開き、GitHubを選択し、Connect branchをクリック。
  3. GitHub認証を行い、アプリケーションを管理しているリポジトリをとブランチをドロップダウンリストから選択し次へ をクリック。
  4. 以下のパラメータを入力し、次へをクリック
  5. App name: デフォルト値
  6. Environment: 前の手順で作成したバックエンド環境名
  7. Select an existing service role ~~: amplifyconsole-backend-role
  8. 保存してデプロイをクリック

Amplifyコンソール画面に戻るとmasterフロントがプロビジョン > ビルド > デプロイ > 検証 の順にステータスが進んでいくのが見えるのですべて終了後に以下の画像の箇所をクリックすることでデプロイされたアプリケーションを閲覧できる。

f:id:ahyt910:20210907113433p:plain
AmplifyコンソールのWebフロントアクセス

実際にWebフロントへアクセスしてみるとReactアプリが動いていることが分かる。 f:id:ahyt910:20210907122253p:plain

最後に

最終的なローカルのディレクトリ構造はここを参照。

amplifyの資材(バックエンド資材)とフロントエンド資材は分けて管理することもできますが今回は同じリポジトリで管理していく方針で進めました。

Reactアプリケーションさえ準備できていれば、Amplifyアプリケーションの作成からデプロイまで30分もかからずに実現できました。

次回はここに認証機能を追加していきたいと思います。

TerraformでSecretsManagerに秘匿情報を登録する。

はじめに

在宅が始まってからやる気が出ない&職場も変わり忙しいかったと言い訳ばかりで久しぶりの投稿です。

本題ですが、最近AWS上にKubernetesをTerraformで構築するプロジェクトに参加してました。その中で秘匿情報を構築資材内から排除しようという流れになり、AWS Secrets Managerを使用することになりました。流れとしてTerraformから登録することになり調べて実装したので書き残しておきたいと思います。

環境

$ terraform --version
Terraform v1.0.1
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v3.48.0

資材

variable "my_secrets" {
  default = {
      key1 = "secret_value1"
      key2 = "secret_value2"
  }
}

resource "aws_secretsmanager_secret" "bunsen" {
  name = "bunsen_blog_secrets"
}

resource "aws_secretsmanager_secret_version" "bunsen" {
  secret_id = aws_secretsmanager_secret.bunsen.id
  secret_string = jsonencode(var.my_secrets)
}

まずは上記資材を作成。

リソースはaws_secretsmanager_secretaws_secretsmanager_secret_versionを使用する。

実行

$ terraform apply
~~~~~~
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

以下のように確認。

f:id:ahyt910:20210704125436p:plain
AWS Secrets Manager

f:id:ahyt910:20210704132123p:plain
AWS Secrets Manager-Secret

うまくSecretが作成されていることを確認。

秘匿情報外だし

variable "secret_value1" {}
variable "secret_value2" {}

locals {
  my_secrets = {
      key1 = var.secret_value1
      key2 = var.secret_value2
  }
}

resource "aws_secretsmanager_secret" "bunsen" {
  name = "bunsen_blog_secrets"
}

resource "aws_secretsmanager_secret_version" "bunsen" {
  secret_id = aws_secretsmanager_secret.bunsen.id
  secret_string = jsonencode(local.my_secrets)
}

最後に変数定義の箇所を変更し、資材から秘匿情報を削除。

$ terraform apply
var.secret_value1
  Enter a value: secret1

var.secret_value2
  Enter a value: secret2

~~~

apply時に秘匿情報を入力させることができる。

後片付け

Secrets Managerは即削除はされず数日間残り続けます。 その間同名のSecretを作成することはできない為、改めてSecretを作成したい場合はSecret名を変更する必要があります。

$ terraform destroy
$ terraform apply
~~~
│ Error: error creating Secrets Manager Secret: InvalidRequestException: You can't create this secret because a secret with this name is already scheduled for deletion.
│ 
│   with aws_secretsmanager_secret.bunsen,
│   on main.tf line 11, in resource "aws_secretsmanager_secret" "bunsen":
│   11: resource "aws_secretsmanager_secret" "bunsen" {
│ 

f:id:ahyt910:20210704141748p:plain
AWS Secrets Manager-Deleted Secret

ansibleでdockerインストールからdocker-compose upまで

概要

dockerとdocker-composeをインストールしてそのままdocker-compose up するplaybookを作ったが、とても詰まったポイントがあったので備忘録として残しておく。

背景

docker-composeで起動しているサービスをVM構築からサービス起動まで一貫したplaybookで構築したい状況でぶつかった。 以下のような処理をするplaybookを作成していた。

  • VM構築
  • dockerをインストール
  • ansibleユーザをdockerグループに追加
  • docker-compose.ymlを転送
  • ansibleユーザでdocker-compose upを実施

しかしなぜか初回構築時に以下のエラーで失敗

fatal: [x.x.x.x]: FAILED! => {"changed": false, "msg": "Error connecting: Error while fetching server API version: ('Connection aborted.', error(13, 'Permission denied'))"}

そして一番の謎ポイントが、もう一度実行すると成功するという点だった。

結論から言うとユーザの所属グループを作成してもそれが適用されるのは一度SSHセッションを切らないとだめだよという話。

環境

注意ポイント

  • ansible version 2.8以降からdocker_composeモジュールが追加されています
  • pipのバージョンが古いとdocker or docker-composeのインストールで失敗します
    • 詳しいバージョンは忘れましたが手元では9.0.3で成功しています

ファイル構造

.
├── hosts
├── launch_services.yml
└── roles
    ├── docker_install
    │   └── tasks
    │       ├── main.yml
    │       └── to_append_ansible_to_docker.yml
    └── launch_containers
        ├── files
        │   └── docker-compose.yml
        └── tasks
            └── main.yml

./launch_services.yml

---
- hosts: docker_hosts
  gather_facts: no
  become: yes
  roles:
    - docker_install

- hosts: docker_hosts
  gather_facts: no
  become: no
  roles:
    - launch_containers

./roles/docker_install/tasks/main.yml

- name: 関連パッケージインストール
  yum:
    name:
      - gcc
      - python2-pip

- name: dockerインストール
  yum:
    name: docker
    state: present

- name: dockerデーモンの起動
  systemd:
    name: docker
    state: started
    enabled: yes

- name: pipインストール
  yum:
    name: python2-pip
    state: present

- name: python用dockerパッケージインストール
  pip:
    name: docker
    state: present

- name: docker-composeインストール
  pip:
    name: docker-compose
    state: present

- include: to_append_ansible_to_docker.yml

./roles/docker_install/tasks/to_append_ansible_to_docker.yml

- name: ansible実行ユーザをdockerグループへ追加
  user:
    name: ansible
    state: present
    groups:
      - docker
    append: yes
  register: ansibles_group

- name: ansible実行ユーザのグループ有効化
  when: ansibles_group is changed
  block:
    - name: sshdの再起動
      systemd:
        name: sshd
        state: restarted

    - name: sshd再起動待機
      wait_for_connection:
        delay: 1
        timeout: 100

`./roles/launch_containers/tasks/main.yml``

- name: docker-composeファイル転送
  copy:
    src: docker-compose.yml
    dest: /home/ansible/docker-compose.yml

- name: コンテナ起動
  docker_compose:
    project_src: /home/ansible/
    state: present

解説

このplaybookでは以下のような処理をたどっています

  • docker, docker-composeインストール
  • ansibleユーザのdocker使用権限追加※1
  • docker-compose.yml転送
  • docker-compose up実行

※1(to_append_ansible_to_docker.yml)はdocker_composeモジュールでbecome: yesを指定する場合は必要ありません

docker, docker-compose インストール

特筆すべき点はあまりありませんがしいて言えば dockerdocker-composeパッケージをインストールできるpip の最低バージョンがあります。

今回のplaybookでは最新のpipを利用しているため問題ありませんがすでにpipがインストールされている環境ではバージョンアップタスクが必要かもしれないです。

ansibleユーザのdocker使用権限追加

ここで記事冒頭の詰まったポイントを回避しています。 docker nodeのsshdを再起動し、ansible nodeとのSSHセッションを強制的に切っています。(もっとスマートなやり方があるのかも、、、)

SSHセッションが切れるとAnsibleは再度SSH接続を仕掛けますが、sshdの再起動が終わっていない場合エラーになってしまう可能性があるため wait_for_connectionモジュールを利用して1秒待機、最大100秒間リトライし続ける処理をかませています。

docker-compose up実行

docker-composeモジュールを利用してコマンドを発行しています。 モジュールの使い方は公式を参照していただきたいのですがdockerを普段から触る方なら直感的に使えるかと思います。 ただ難点があるとすればなかなか要求が厳しいです

  • docker関連
    • Docker API >= 1.20 (docker configで確認)
    • docker-compose >= 1.7.0
  • pythonモジュール関連
    • Docker SDK
      • python2.6: docker-py
      • python2.7: docker >= 1.8.0
    • PyYAML >= 3.11
  • ansible関連
    • ansible >= 2.8

新規構築ではなくありものでこのモジュールを使う場合、アップグレード処理を入れる必要が出てくるかもしれません。

ansibleの曲者synchronizeモジュール

概要

Ansibleを使っての運用をする機会が増えてきた今日このごろ。 被管理ホストでバックアップをするために、ファイルをansibleホストに転送しようとしたときにこのsynchronizeモジュールに出会いました。

最初のうちは便利だなと思い公式ページを見ていたが 使って/調べていくにつれてやけに自身の認識と実際の動作に差が開いていった。 マジで使いづらい。。。 ということでまとめます。

synchronizeモジュールとは

公式によると「rsyncのラッパーである」と書かれている。 つまり裏ではrsyncが動作しているとのこと。

rsyncは管理/被管理ノードで起動し、その間でファイルの転送を実施する。 そのため両ホストにrsyncがインストールされている必要がある。

rsync実行にはsudo権限が必要であるためansibleユーザにsudo権限が必要。 ※パスワードなしsudo権限でないとNG。パスワードありsudoだとrsyncが起動できずエラーで終了する。

動作パターン①

localhostで動作し、被管理ホストにローカルファイルを送り込む。

- hosts: target
- tasks:
  - synchronize:
      mode: push
      src: <local_path_to_file>
      dest: <remote_path_to_file>

動作パターン②

localhostで動作し、被管理ホストからローカルにファイルを取り込む。

- hosts: target
- tasks:
  - synchronize:
      mode: pull
      src: <remote_path_to_file>
      dest: <local_path_to_file>

動作パターン③

被管理ホストで動作し、localhostにファイルを送り込む(結果はパターン②と同じ)

- hosts: target
- tasks:
  - synchronize:
      mode: push
      src: <remote_path_to_file>
      dest: <local_path_to_file>
    delegate_to: target

動作パターン④

被管理ホストで動作し、localhostからファイルを取り込む(結果はパターン①と同じ)

- hosts: target
- tasks:
  - synchronize:
      mode: pull
      src: <local_path_to_file>
      dest: <remote_path_to_file>
    delegate_to: target

batfish/allinoneコンテナにおけるnotebookデータの永続化

概要

batfish/allinoneのコンテナを使用してbatfishをお勉強中。しかし、コンテナを落とし上げするたびにせっかく作ったデータがなくなってしまう。

かといって毎回作成したnotebookをダウンロードして上げなおしてというのはめんどくさいのでホスト側にマウントすることにした。

環境

  • Windows10 10.0.16299
  • Docker for Windows version 18.09.2, build 6247962
  • batfish/allinone c5c630d4bdd3

方法

  1. ホスト側にマウント先のディレクトリを作成する
    1. mkdirでもエクスプローラーからでもなんでもいいのでデータの保存先ディレクトリを作成する
  2. 現存するnotebookをコンテナ内部から救出する
    1. 必要なnotebookにWEBブラウザでアクセスする
    2. JupyterのFile>Download as>Notebook(.ipynb)を選択し、作成したマウント先のディレクトリに保存する
    3. 救出するnotebookはないがデフォルトで入っているサンプルは残したい場合
      • pybatfishからpullすると中に同じものが入ってるから丸ごとマウント先のディレクトリにコピーする
  3. コンテナを停止する
    1. docker stop <docker id or name>でコンテナを停止する
  4. コンテナを起動する
    1. docker run -v <マウント先ディレクトリの絶対パス>:/notebooks -p 8888:8888 batfish/allinoneでコンテナを起動する

まとめ

docker exec -it <docker id or name> /bin/bashでコンテナ内部をウロチョロできて楽しかったです!

docker run -p において発生するポートフォワード失敗エラー

概要

ネットワーク機器のコンフィグ解析ツールbatfish/allinoneのコンテナを起動しようとしたとき以下のエラーが発生した。

> docker run -p 8888:8888 batfish/allinone
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: driver failed programming external connectivity on endpoint gracious_poincare (***): Error starting userland proxy: mkdir /port/tcp:0.0.0.0:8888:tcp:172.17.0.2:8888: input/output error.
ERRO[0000] error waiting for container: context canceled
  • 公式の手順通りやってるのに...
  • 昨日まではうまくいっていたのに...

ということで少し調査

原因

どうも2通りの原因があるようです

  1. ホスト側のポートが他のサービスにより占有されている
    • psなどのコマンドで使用したいポートが占有されているか調べる
  2. Dockerの設定に過去のコンテナ情報が残ってしまっている
    • 原因1 でない場合

解決策

原因のパターンにより異なります。

1. ホスト側のポートが他のサービスにより占有されている

  • 占有しているサービスを停止する
  • ホスト側のフォワーディングポートを変更する

2. Dockerの設定に過去のコンテナ情報が残ってしまっている

  • Dockerの再起動

まとめ

なんかあんまりしっくりこないね...