20200316のAWSに関する記事は19件です。

[Amazon Linux 2]公開鍵認証からパスワード認証へ変更

内容

EC2への接続方法を公開鍵認証からパスワード認証へ変更する

類似記事
(Amazon Linux 2の)sshd_configとauthorized_keysを学ぶ

手順

対象のEC2へSSH接続して、ユーザをrootへ切り替えます。

sshd_configのバックアップ取得

設定変更を行う前には必ずバックアップを取得しておきます。

# cd /etc/ssh/
# pwd
/etc/ssh

# ls -l
total 608
...
-rw-r--r-- 1 root root       2276 Mar 15 12:25 ssh_config
-rw------- 1 root root       3977 Mar 15 12:04 sshd_config
...

# cp -p sshd_config sshd_config_yyyymmdd
# ls -l
total 608
...
-rw-r--r-- 1 root root       2276 Mar 15 12:25 ssh_config
-rw------- 1 root root       3977 Mar 15 12:04 sshd_config
-rw------- 1 root root       3977 Mar 15 12:04 sshd_config_yyyymmdd
...

sshd_config設定変更

sshd_configの設定を変更していきます。
(変更前)
#PubkeyAuthentication yes
PasswordAuthentication no

(変更後)
PubkeyAuthentication no
PasswordAuthentication yes

# vi sshd_config
# cat sshd_config | grep PubkeyAuthentication
#PubkeyAuthentication yes
PubkeyAuthentication no

# cat sshd_config | grep PasswordAuthentication
PasswordAuthentication yes
#PasswordAuthentication no

# systemctl restart sshd.service

viコマンドで設定変更を行い、cat+grepで変更箇所が問題なく変更されていることを確認します。
その後、systemctlコマンドでsshd.serviceを再起動します。

パスワード認証での接続確認

Teratermを使用して、パスワード認証でログインできるかを確認します。
その際、現在の接続は保ったまま行ってください。

IPアドレスを入力してOKを押下します。
ss_000.JPG

ユーザ名とパスワードを入力してブレインパスワードを使う(L)を選択して接続します。
ss_001.JPG

接続できました。
ss_002.JPG

終わり

これでパスワード認証へ変更が出来た。
出来たがパスワード認証はあまりよろしくないので元に戻しましょう。

# cp -p  sshd_config_yyyymmdd sshd_config
# systemctl restart sshd.service
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaプロキシ統合でmultipartのフォームデータをパースする

ファイル添付とかmultipartで送信されてくることがあるので、そのパース方法。
busboyというライブラリを次のように使うことで実現可能。

eventはlambda関数の入力。

    if (event.headers['content-type'] && (event.headers['content-type'] as string).includes('multipart/form-data')) {
        console.log('IT IS MULTIPART');
        const input: CreateMeProfileInput = {};
        const busboy = new Busboy({
            headers: event.headers,
            defCharset: 'utf8'
        });
        return new Promise((resolve, reject) => {
            busboy.on('file', (fieldname: any, file: any, filename: any, encoding: any, mimetype: any) => {
                console.log('File [' + fieldname + ']: filename: ' + filename + ', encoding: ' + encoding + ', mimetype: ' + mimetype);
                file.on('data', function (data: any) {
                    util.inspect(data);
                    console.log(typeof data);
                    console.log('File [' + fieldname + '] got ' + data.length + ' bytes');
                });
                file.on('end', function () {
                    console.log('File [' + fieldname + '] Finished');
                });
            });
            busboy.on('field', function (fieldname: string, value: any, fieldnameTruncated: any, valTruncated: any, encoding: any, mimetype: any) {
                console.log(util.inspect(value));
                console.log(value.toString('utf8'));
                console.log('Field [' + fieldname + ']: value: ' + util.inspect(value) + ` encoding:${encoding}, valTruncated:${valTruncated}, mimetype:${mimetype}`);
            });
            busboy.on('finish',  () => {
                console.log('Done parsing form!');
                console.log(input);
                resolve(input);
            });
            // @ts-ignore
            busboy.on('error',  (err) => {
                console.log('busboy parse error');
                console.log(input);
                reject(err);
            });
            busboy.write(event.body); // 第二引数いらない
            busboy.end();
        });
    } 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IAMロールとEC2でcronからAWS CLIを実行

IAMロールをアタッチしたEC2で、cronからシェルスクリプト実行でハマった話。

環境

  • RHEL7.7
  • aws cli/1.18.8
  • IAMロール(S3アップロード権限有)

やりたいこと

cronからS3アップロード処理を記載したシェルスクリプトを実行し、EC2にあるデータをS3にアップロードしたい。

エラー内容

EC2にSSHでログインし手動でシェルスクリプトを実行可能だが
cronから実行すると「aws コマンドが見つかりません」でエラーとなる。

解決方法

  • whichコマンドでAWS CLIのフルパスを確認し、コマンドはフルパスで記載する。
  • シェルスクリプトの冒頭の呼び出しシェルに「-l」オプションを追加
sample.sh
#!/bin/bash -l

/usr/local/bin/aws s3 cp ローカルファイルパス S3バケット

参考記事

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

Amazon Elasticsearch Serviceで検索できる状態まで最速で立ち上げる

はじめに

とにかく始めてみることを目的に、Elasticsearchで全文検索できる状態を作るまでの最短距離を説明します。Amazon Elasticsearch Serviceを使います。Amazon Elasticsearch Serviceについては下のセクションを参照。
大まかな手順としては、

  1. AWS上でElasticsearchサービス環境の構築
  2. KibanaでElasticsearchにデータの投入

となります。

作成する環境

  • パブリックに公開されたシングルインスタンスのElastisearch環境(URLにはランダムな文字列が入ります)
  • インターネットから検索可能で、管理操作は特定IPアドレスからのみに制限
  • t2.smallインスタンスを使い無料枠内(他サービスを使っていなければ)
  • Elasticsearchのバージョンは7.1

事前準備

  • インデックス名を決めておく(Elasticsearchのインデックスはデータの入れ物となるもので、RDBのDB名にあたるものです。)
  • ドメイン名を決めておく(Amazon Elasticsearchでのドメイン名は、Elasticsearchクラスターを識別する名前で、1つのURLを持ちます。この下にElasticsearchの複数のインデックスを持つことができます。)
  • 管理アクセスをするPCのグローバルIPアドレスを確認しておく

手順

AWS上でElasticsearchサービス環境の構築

(「Amazon Elasticsearch Serviceを触ってみた」が参考になります。)
基本的に、ドメイン名を入れて、開発およびテスト環境を選び、インスタンスサイズを入れて、アクセスポリシーを設定する以外はデフォルトのままです。

  1. AWS管理コンソールにログインし東京リージョン(ap-northeast-1)に切り替え
  2. 「新しいドメインの作成」ボタン
  3. 「デプロイタイプの選択」で「開発およびテスト」を選択(シングルAZ)
  4. 「バージョン」はデフォルトのまま
  5. 「次へ」ボタン
  6. 「ドメインの設定」 -> 決めていたドメイン名の入力
  7. 「データノード」でt2.small.elasticsearchを選択、ノードの数は1
  8. 「データノードストレージ」はデフォルトの1インスタンスのまま
  9. 専用マスターノードは有効化しない(チェックを入れない)
  10. スナップショットを取る時間をUTCで指定(e.g. 19:00 UTCが04:00 JST)
  11. 任意のElasticsearchクラスター設定はデフォルトのまま
  12. 「次へ」ボタン
  13. 「ネットワーク構成」でパブリックアクセスを選択
  14. 細かいアクセスコントロールはt2.smallを選ぶと選択できないためそのまま選択しない
  15. Amazon Cognito認証を有効化はチェック入れない(別記事にて認証設定)
  16. アクセスポリシーでは「JSON定義のアクセスポリシー」で下のアクセスポリシー例を入れる
  17. 暗号化はデフォルトのドメインへのトラフィックにHTTPSのまま
  18. 「次へ」ボタン
  19. 「確認」ボタン
  20. ドメインのステータスがアクティブになるまで待つ

アクセスポリシー例

検索はオープン、更新や管理をIPアドレス制限
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:ESHttpGet",
      "Resource": "arn:aws:es:ap-northeast-1:[アカウントID]:domain/[ドメイン名]/*"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "es:ESHttpDelete",
        "es:ESHttpPut",
        "es:ESHttpPatch",
        "es:AddTags",
        "es:Create*",
        "es:Delete*",
        "es:Describe*",
        "es:Get*",
        "es:List*",
        "es:Purchase*",
        "es:Remove*",
        "es:Update*"
      ],
      "Resource": "arn:aws:es:ap-northeast-1: [アカウントID]:domain/[ドメイン名]/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "[アクセス元IPアドレス]/32"
        }
      }
    }
  ]
}

KibanaでElasticsearchにデータの投入

  1. AWS管理コンソールのElasticsearchの画面から作成したドメイン名をクリックして開く
  2. Kibanaの箇所に記載されたURLでKibanaを開く(通常URLは、https://search-[ドメイン名]-[ランダムな文字列].ap-northeast-1.es.amazonaws.com/_plugin/kibana/
  3. KibanaのWelcome画面で「Explore on my own」をクリック
  4. 左側のスパナアイコン?(=Dev Tools)をクリック
  5. Get to Workボタンを押す
  6. 左側にGET _searchと入力し、再生ボタン▶︎を押すか、CTRL/Command+Retrunで実行して右側に結果が出るか確認する
  7. 下のデータ投入例のように記述し(インデックス名はcityindexとする)、POSTを書いた行の再生ボタンを押してデータを入れてみる(Elasticsearchはデータ投入時に自動でmapping作成=RDBのテーブル定義をしてくれます)
  8. GET cityindex/_searchで投入結果を確認する
  9. インデックスの削除は、DELETE cityindex
  10. データを複数投入する際は下のデータ投入例の複数件データ投入の例
  11. Mapping(=RDBのテーブル定義)の確認はGET cityindex/_mapping
  12. 手動でのマッピング定義は、下のデータ投入例のmapping定義の例

インデックス名をcityindexとしたときのデータ投入例

1件データ投入
POST cityindex/_doc
{
  "cityname": "東京",
  "countryname": "日本",
  "Rank": "19",
  "population": "927"
}
複数件データ投入
POST /cityindex/_bulk
{"index":{"_index":"cityindex"}}
{"cityname":"上海","countryname":"中国","rank":"1","population":"2415"}
{"index":{"_index":"cityindex"}}
{"cityname":"北京","countryname":"中国","rank":"2","population":"1861"}
{"index":{"_index":"cityindex"}}
{"cityname":"ムンバイ","countryname":"インド","rank":"3","population":"1839"}
{"index":{"_index":"cityindex"}}
{"cityname":"デリー","countryname":"インド","rank":"4","population":"1634"}
{"index":{"_index":"cityindex"}}
{"cityname":"カラチ","countryname":"パキスタン","rank":"5","population":"1491"}
{"index":{"_index":"cityindex"}}
{"cityname":"イスタンブール","countryname":"トルコ","rank":"6","population":"1464"}
{"index":{"_index":"cityindex"}}
{"cityname":"コルカタ","countryname":"インド","rank":"7","population":"1405"}
{"index":{"_index":"cityindex"}}
{"cityname":"ラゴス","countryname":"ナイジェリア","rank":"8","population":"1312"}
{"index":{"_index":"cityindex"}}
{"cityname":"モスクワ","countryname":"ロシア","rank":"9","population":"1222"}
{"index":{"_index":"cityindex"}}
{"cityname":"広州","countryname":"中国","rank":"10","population":"1208"}
事前mapping定義
PUT cityindex
{
    "mappings" : {
      "properties" : {
        "cityname" : {
          "type" : "text"
        },
        "countryname" : {
          "type" : "text"
        },
        "rank" : {
          "type" : "integer"
        },
        "population" : {
          "type" : "integer"
        }
      }
    }
}

ここまで。
データ操作については、「はじめての Elasticsearch」等を参照。

Amazon Elasticsearch Serviceとは

Amazon Elasticsearch Serviceは、AWSが提供するフルマネージドのElasticsearchサービスです。Elasticsearchを簡易に始めるには非常に便利です。Elasticsearchの特徴についてはこちらに詳しいです(全文検索エンジン「Elasticsearch」を調べて使ってみた色々まとめ)。Elasticsearchの様々な派生(Amazon Elasticsearch Serviceもその1つ)についてはこちら(Amazon Elasticsearch Service の認証・認可に関する面倒くさい仕様をなるべくわかりやすく説明する)。

参考リンク

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

Cloud9のワークスペースに入れなくなってQiitaを読んでも解決しなかった時の対処法と最終手段

無事ワークスペースに入れなくなったあなた、この記事を読む前に
https://qiita.com/somarihair/items/759d525153ae68ce4a7f
https://qiita.com/Atsushi_/items/b38c18feac4708164696
この2つの記事を先に読むことをお勧めします。

起こった問題

Chromeを起動して最初にワークスペースにアクセスしようとすると

This is taking longer than expected. If you think there might be an issue, contact AWS Support.
It might be caused by VPC configuration issues. Please check documentation.

または

This is taking longer than expected. If you think there might be an issue, contact AWS Support.

とエラーメッセージが表示され、上記の記事のようにインスタンスページに行くと

インスタンスデータ You are not authorized to perform this operation. の取得中にエラーが発生しました。

と表示される。
検証ページのコンソールには

Access to XMLHttpRequest at '{リンク}' from origin 'https://ap-northeast-1.console.aws.amazon.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

そして

ERROR Error: Failed to retrieve resource from {リンク} with code 0
at XMLHttpRequest.xhr.onerror (https://d139ht92gga9c6.cloudfront.net/c9-73b98c857618-ide/build/configs/ide/@aws/cloud9/configs/ide/environment-default.js:951:30)

さらに

Failed to load resource: net::ERR_FAILED

この3つのエラーメッセージが繰り返し表示されエラーが100件近く溜まる。

対処法

本質的な解決方法は最初の2記事を見る、そしてAWSトラブルシューティングに載っている通り管理者に問い合わせることです。

僕の場合はどちらも効果がなかったのでどちらかというと回避策的な方法を2つ書いておきます。
1. Chromeのアカウントを変えてアクセスする。
2. Safari等他のブラウザからアクセスする。

今回の問題の根本が何かはわかりませんでしたが、アカウントのcookieやキャッシュ削除でも意味を為さなかったあたり、Chrome自体に問題があったようです。

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

AWS CLIでアカウントIDとリージョンを取得する

EC2上でシェルを書く時、何かとAWS CLIでリージョンやアカウントIDを使うことが多いですよね。

ただ、そのままアカウントIDを111222333444のように埋め込んだり、リージョンをap-northeast-1と書くのは、様々な環境で使い回す上ではなるべく避けたいと思ったので取得してみました。

アカウントIDの取得方法

アカウントIDはaws sts get-caller-identityを使用して取得できます。

$ aws sts get-caller-identity
{
    "Account": "111222333444", 
    "UserId": "AAAABBBBCCCCDDDDEEEE", 
    "Arn": "arn:aws:iam::111222333444:user/sample"
}

このようにAccountやUserID、IAMのARNがJSONで返ってくるので、ここから取得してあげましょう。

先程のコマンドからjqを使用してパースして抜き出します。

jqがない場合はインストールして下さい。
参考:jq コマンドの Linux への速攻インストール

$ aws sts get-caller-identity | jq -r '.Account'
> 111222333444

こんな感じでアカウントIDはOK

※2020/03/17追記
CLIの--queryオプションであれば、jqを使用せずとも抜き出せるとコメント頂きました。こちらの方が簡単ですね!

$ aws sts get-caller-identity --query 'Account' --output text

リージョンを取得する方法

リージョンは以下のコマンドで取得してみます。

$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e s/.$//
> ap-northeast-1

インスタンスのメタデータからAZの情報を取得できるので、最後の1文字を削除してあげればリージョン名になります。

シェルで使用する

AWSではサービスの情報を取得したり、何か情報をPUTしたりする際ARNを指定することが多いんですが、これの中にアカウントIDやリージョンがよく必要になります。

例えばSNSのトピックを指定する際のARNはこんな感じ。
arn:aws:sns:ap-northeast-1:111222333444:sample-topic

これは見ればわかると思いますが、
arn:aws:sns:【リージョン】:【アカウントID】:【トピック名】

のような法則で作られる文字列です。

シェルの中で変数に入れたい場合はこんな感じに変数で書いてあげましょう。

SNS_TOPIC_NAME="sample-topic"
REGION=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e s/.$//` 
ACCOUNT_ID=`echo aws sts get-caller-identity | jq -r '.Account'` 

SNS_TOPIC_ARN="arn:aws:sns:${REGION}:${ACCOUNT_ID}:${SNS_TOPIC_NAME}"
echo ${SNS_TOPIC_ARN}

> arn:aws:sns:ap-northeast-1:111222333444:sample-topic

こうすると他のアカウントや別リージョンで同じシェルを使いたい場合でも、トピック名だけを修正すればいいのでとても修正箇所が少なくて済みます。

誰かのお役に立てれば幸いです。

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

【丁寧解説】Elastic Beanstalkを使ってRailsをサクッとEC2にデプロイしよう

初学者がAWSにデプロイする時、めちゃめちゃ設定あって挫けそうになりませんか?

ええ、わかります。。。
自分も大量のエラーと戦いながら、なんとかデプロイをした経験があります。

新しくアプリを作った時にCI/CDを使ってどうデプロイしようか?と
調べていた時にElastic Beanstalkを知りました。

実際に使ってみて、とても簡単にデプロイ出来たし、
なによりEC2へデプロイすることの全体像をサクッとつかめるなって思ったので
初学者向けに丁寧に解説しようと思って記事を書きました。

デプロイに挫けそうになった方は
ぜひElastic Beanstalktを使って試してみてください!

Elastic Beanstalkt とは?

AWS-Elastic-Beanstalk@4x.png

Elastic Beanstalk では、アプリケーションを実行しているインフラストラクチャについて学習することなく、AWS クラウドでアプリケーションをすばやくデプロイし、管理できます。Elastic Beanstalk は、選択肢を狭めたり制御を制限したりすることなく、管理の複雑さを軽減します。アプリケーションをアップロードするだけで、Elastic Beanstalk が自動的に容量のプロビジョニング、負荷分散、拡張、およびアプリケーションの状態のモニタリングといった詳細を処理します。
AWS Elastic Beanstalk とは

簡単に言えば、インフラ詳しくなくてもアプリをEC2にデプロイできて、
よくあるアプリのインフラ設計で勝手にチューニングしてくれるよってやつです。

aeb-architecture2.png

料金に関しても使ったAWSリソースのみ(今回の記事ではEC2とRDS、S3)なので、
無料枠内で全然試すことができます。

詳しい技術については丁寧に解説してくれている記事が
たくさんあると思うので、興味がある人はそちらをご覧ください。

この記事では0からデプロイができるようになることをフォーカスして解説していきます!

はじめに

前提

  • AWSアカウントを持っている
  • Railsアプリを作成できる
  • 簡単なコマンドがわかっている

大まかな流れ

  1. AWS CLIのインストール・セットアップ
  2. AWS EB CLIのインストール・セットアップ
  3. Railsアプリの用意・デプロイの事前準備
  4. RailsアプリをEC2にデプロイ

この記事のゴール

  • EC2にRailsアプリをデプロイできるようになる
  • ファイルを更新して、再度デプロイを行い、アプリを継続的に更新できること

AWS CLI のインストール・セットアップ

1. AWS CLI 用の IAM を作成

まずAWS CLI用のIAMを作成し、CLIから操作できるようにします。

AWS: IAMこちらからIAMのページに飛んでください。

サイドバーの ユーザーユーザーを追加 をクリックしてAWS CLI用のユーザーを追加します。
iam_001.jpg

ユーザー名はわかりやすいように aws-cli としておきます。

アクセスの種類はCLIからなので、プログラムによるアクセスにチェックを入れます。

次のステップに進みます。

iam_002.jpg

アクセス権限の付与は後ほどするので、そのまま次のステップへ。

iam_003.jpg

特にタグで管理をしていないので、そのまま次のステップへ。

iam_004.jpg

アクセス権限を飛ばしたのでWarningが出ていますが、後ほど追加するのでそのまま次のステップへ。

iam_005.jpg

これでユーザー作成が完了しました。

アクセスキーIDシークレットアクセスキー は後ほど使うので、コピーしておいてください。

iam_006.jpg

2. AWS CLI をインストール

Homebrewをつかって AWS CLI をインストールします。

$ brew install awscli

インストールが問題なくされたか確認します。

$ aws --version
aws-cli/2.0.0 Python/3.8.1 Darwin/19.3.0 botocore/2.0.0dev4

バージョンが表示されればokです。

3. AWS CLI のデフォルト設定を行う

続いてはAWSへのアクセス権限の付与とデフォルト情報を入力していきます。

aws configureを入力すると項目が4つ表示されるので、順に入力していきます。

$ aws configure
AWS Access Key ID [None]: *********
AWS Secret Access Key [None]: ******************
Default region name [None]: ap-northeast-1
Default output format [None]: json

AWS Access Key ID
先程作成したユーザーのアクセスキーID

AWS Secret Access Key
先程作成したユーザーのシークレットアクセスキー

Default region name
使用するリージョン名(東京: ap-northeast-1)

Default output format
アウトプットの表示形式(jsonやtextなど)

4. AWS CLI の設定を確認する

それぞれ設定ファイルをcatで確認します。

下記のような表示になれば問題ありません。

$ cat ~/.aws/credentials
[default]
aws_access_key_id = *********
aws_secret_access_key = ******************

$ cat ~/.aws/config
[default]
region = ap-northeast-1
output = json

変更する場合やユーザーを追加したい時は、上記ファイルに追記すれば可能です。

詳しくはAWS CLI の設定をご覧ください。

AWS EB CLIのインストール・セットアップ

AWS EB CLI をインストール

Elastic Beanstalk用のCLIをインストールします。

$ brew install awsebcli

正しくインストールされたか、確認しましょう。

$ eb --version
EB CLI 3.17.1 (Python 3.8.2)

バージョンが表示されたらインストール完了です。

EB のアクセス権限を付与する

CLI上からElastic Beanstalkを操作できるようにアクセス権限を付けていきましょう。

1. EBアクセス権限のグループを作成する

まずはElastic Beanstalkのアクセス権限をつけたグループを作っていきます。

AWS: IAM からグループ新しいグループの作成 をクリック。

iam_eb_001.jpg

グループ名に eb-cli を入力し、次のステップへ。

iam_eb_002.jpg

検索窓に elasticbeanstalk と入力すると関連のポリシーが出てきます。

FullAccess にチェックを入れて、次のステップへ。

iam_eb_003.jpg

ポリシーが追加されているか確認し、グループの作成をクリック。

iam_eb_004.jpg

これでElastic Beanstalkのアクセス権限があるグループが作成できました。

2. ユーザーをグループに追加する

続いては冒頭に作ったAWS CLI用ユーザーをグループに追加します。

サイドバーのユーザーaws-cli をクリック。

iam_eb_005.jpg

グループユーザーをグループに追加 をクリック。

iam_eb_006.jpg

先程作った eb-cli にチェックを入れ、グループに追加をクリック。

iam_eb_007.jpg

これでAWS CLI用ユーザーがグループに追加され、EB CLIからElastic Beanstalkにアクセスできるようになりました。

iam_eb_008.jpg

Railsアプリの用意・事前準備

Railsアプリを用意する

必要に応じてRailsのアプリを用意してください。

Rubyは2.6.5を使用してください。

EC2でのRubyのVersionが2.6.5のため、異なるとエラーが出ます。

自分の各種version
Ruby: 2.6.5
Rails: 5.2.4.1
Bundler: 2.1.4

自分はシンプルなscaffold構成で作成したものを用意しました。

5.2系統を普段使っているのでRailsのバージョン指定しています。ご自身の環境によって変更してください。

$ bundle exec rails _5.2.4.1_ new eb_sample_app -d postgresql
$ cd eb_sample_app
$ bundle exec rails g scaffold user name:string
$ bundle exec rails db:create
$ bundle exec rails db:migrate

サーバーを立ててみて問題なくアプリが作成できているか確認します。

$ bundle exec rails s

localhost:3000/users を開いて、下記のようなサイトが開けば問題ありません。

SS_ 2020-03-15 22.36.37.jpg

このままではトップページになにも表示されないので、routes.rb にrootを追加します。

Rails.application.routes.draw do
  root 'users#index' # << rootを追加
  resources :users
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

localhost:3000 を開いてusersと同じページが表示されれば設定完了です。

Rails Serverはこれ以上使わないので、落としておきます。

この時点でコミットしておきます。

~/eb_sample_app
$ git add .
$ git commit -m "first commit"

デプロイの準備をする

eb init で初期設定を行う

eb init を入力して初期設定をしていきます。

アプリのトップディレクトリで行ってください。

~/eb_sample_app
$ eb init

いつくかの項目を入力して設定します。

実行中に ERROR: NotAuthorizedError が出たら?

設定中に ERROR: NotAuthorizedError が出たらアクセス権限エラーになっています。

ERROR: NotAuthorizedError - Operation Denied.
User:arn:aws:iam::476091318231:user/aws-cli is not authorized to perform:elasticbeanstalk:CreateApplication on resource:
arn:aws:elasticbeanstalk:ap-northeast-1:476091318231:application/eb_sample_app

IAMの設定が有効になるには3~5分ほどかかるので、コーヒーブレイクでもはさみましょう。

10分くらいまってもエラーならIAMの設定が間違えているので、再度IAM設定を行ってください。

それでは入力を進めていきます。

使用するリージョンは東京リージョンを使うので、
9) ap-northeast-1 : Asia Pacific (Tokyo)9 を入力

Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
18) eu-north-1 : EU (Stockholm)
19) ap-east-1 : Asia Pacific (Hong Kong)
20) me-south-1 : Middle East (Bahrain)
(default is 3): 9

使用するアプリケーションを選択します。

[ Create new Application ]を選択。

※ 初回作成だと[ Create new Application ]のみ

Select an application to use
1) rails-eb
2) [ Create new Application ]
(default is 2): 2

ElasticBeanstalkのコンソール上でのアプリ名です。

特に変更がなければ、なにも入力せず空でエンター。

Enter Application Name
(default is "eb_sample_app"): 

自動的にコードを解析して、使用する言語を提案してくれます。

Rubyで良いので、空でエンター。

It appears you are using Ruby. Is this correct?
(Y/n): 
  • 使用するプラットフォームとバージョンを選択

RailsのデフォルトであるPumaで構築したいので、2) Ruby 2.6 (Puma)2 を入力

※ Rubyのバージョンは使用しているものを選んでください。

Select a platform version.
1) Ruby 2.6 (Passenger Standalone)
2) Ruby 2.6 (Puma)
3) Ruby 2.5 (Passenger Standalone)
4) Ruby 2.5 (Puma)
5) Ruby 2.4 (Passenger Standalone)
6) Ruby 2.4 (Puma)
7) Ruby 2.3 (Passenger Standalone)
8) Ruby 2.3 (Puma)
9) Ruby 2.2 (Passenger Standalone)
10) Ruby 2.2 (Puma)
11) Ruby 2.1 (Passenger Standalone)
12) Ruby 2.1 (Puma)
13) Ruby 2.0 (Passenger Standalone)
14) Ruby 2.0 (Puma)
15) Ruby 1.9.3
(default is 1): 2
  • CodeCommit(AWSが提供してるGitHubのようなもの)を使うかどうか?

使わないので、そのまま空でエンター。

Do you wish to continue with CodeCommit? (y/N) (default is n): 
  • インスタンスにSSHで接続するかどうか?

接続するので、そのまま空でエンター。

Do you want to set up SSH for your instances?
(Y/n):
  • 使用するキーペアを設定します。

すでにキーペアを持っている人は、空のままエンター。

初めての人は[ Create new KeyPair ]のみなので、作成に進んでいきます。

# 作成済
Select a keypair.
1) ec2-keypair
2) [ Create new KeyPair ]
(default is 1): 

# 初めての人
Select a keypair.
1) [ Create new KeyPair ]
(default is 1): 

Create new KeyPairに進むとSSHで接続するキーペアを作成していきます。

キーペアの名前を付けていきます。

好きな名前を付けてください。

Type a keypair name.
(Default is aws-eb): aws-eb

公開鍵と秘密鍵が作成されました。

続いて、鍵にパスワードをつけていきます。
空で入力するとパスワードなしで発行されます。

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

下記のような表示になれば作成完了です。

The key fingerprint is:
SHA256:xxxxxxxxxx aws-eb
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|      .          |
|     . +         |
|    = * S        |
|   o B E .       |
|o.+ .   +   .    |
|O=   =o+.=.+     |
|X++      .oo.    |
+----[SHA256]-----+

もう一度パスワード入力を求められるので入力します。

Enter passphrase:
WARNING: Uploaded SSH public key for "aws-eb" into EC2 for region ap-northeast-1.

EC2へのSSH接続が確認されキーペアの連携ができました。

これで初期設定が完了です。

.elasticbeanstalk フォルダが作成され、中に config.yml という設定ファイルが作成されました。

eb_init_directory.jpg

config.yml の中身は以下のようになっています。

~/eb_sample_app/.elasticbeanstalk/config.yml
branch-defaults:
  master:
    environment: null
    group_suffix: null
global:
  application_name: eb_sample_app
  branch: null
  default_ec2_keyname: aws-eb
  default_platform: Ruby 2.6 (Puma)
  default_region: ap-northeast-1
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: null
  repository: null
  sc: git
  workspace_type: Application

eb createの前に各種問題への対処

続いてeb createをするのですが、そのまま実行すると3つのエラーが発生します。

それぞれどんなエラーが発生するのか解説し、事前に対応します。

1. bundlerが古い問題

そのまま実行すると自動で実行される bundle install でエラーがでます。

エラーの内容は指定されているBundlerが無いぞ!って言われてます。

ERROR   [Instance: i-02e2bc303363c3671] 
Command failed on instance. Return code: 1 Output: (TRUNCATED)...your system, 
run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`

これは初期に生成されるEC2環境のBundlerのバージョンが1.16.0のため、最新のBundleが無いんですね。

[ec2-user@ip-172-31-2-55]
$ bundle -v
Bundler version 1.16.0

SSHで接続してgem install bundler:2.1.4でインストールしてもいいのですが、それだと毎回設定が必要です。

しかもこのままではAuto Scalingで2台目が立ち上がったときも設定が必要で意味がありません。

ElasticBeanstalkには初回にコマンドを実行する設定があるので、そちらで必要となる処理を記載していきます。

gem install bundlerの実行設定をする

アプリのトップディレクトリに.ebextensionsというファイルを作成します。
.は必要なので気をつけてください!

このファイルに~.configというファイルを作成すると実行時に読み込んで実行してくれます。

~/eb_sample_app
$ mkdir .ebextensions
$ cd .ebextensions
$ touch gem_install_bundler.config

中身を下記の内容で作成します。

~/eb_sample_app/.ebextensions/gem_install_bundler.config
files:
  "/opt/elasticbeanstalk/hooks/appdeploy/pre/09_gem_install_bundler.sh" :
    mode: "000775"
    owner: root
    group: users
    content: |
      #!/usr/bin/env bash

      EB_SCRIPT_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k script_dir)
      EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
      . $EB_SCRIPT_DIR/use-app-ruby.sh

      cd $EB_APP_STAGING_DIR
      gem update --system
      gem install bundler -v 2.1.4

内容は、自動で実行されるbundle installの前に
gem update --system
gem install bundler -v 2.1.4
を実行するコードになります。

gem install bundler -v 2.1.42.1.4はご自身のBundler versionを指定してください。

Gemfile.lockの一番下に書いてあるBUNDLED WITHに合わせないとエラーになります。

Gemfile.lock
BUNDLED WITH
   2.1.4

3. master.keyが無い問題

続いてはrails credentials:editが発生します。

ERROR   [Instance: i-012e1680a4956ac39] 
Command failed on instance. 
Return code: 1 
Output: (TRUNCATED)...tring with `rails credentials:edit`

ElasticBeanstalkにデプロイすると自動でproduction環境になります。

その際にconfig/master.keyもしくはRAILS_MASTER_KEYを参照しているのですが、
config/master.keyは秘密鍵なのでgitignoreでgitにcommitされないようになっています。
この情報がないためエラーになります。

詳しくはこの記事では触れないため、下記のサイトを参照してください。

Rails5.2から追加された credentials.yml.enc のキホン

Serverに環境変数であるRAILS_MASTER_KEYを設定しましょう。

まずはconfig/master.keyを開きます。

~/eb_sample_app/config/master.key
da7xxxxxxxxxxxxxxxxxdf1b1de

中に文字の羅列があるので、そちらをコピーしておいてください。

eb create時に--envvars のオプションをつけると環境変数を設定できます。

eb create実行する時に以下のコマンドを付ければ解決です。
※まだ実行しないでください。

eb create --envvars RAILS_MASTER_KEY=da7xxxxxxxxxxxxxxxxxdf1b1de

複数つける場合はカンマ区切りでつけることができます。

--envvars key=value,key2=value2,key3=value3

3. DBが無い問題

続いてのエラーはDBがなくて db:migrate が出来ないよ!っていうエラーです。

ERROR   [Instance: i-048e3c74bf17b2499] 

Command failed on instance. 
Return code: 1 Output: (TRUNCATED)...ly and accepting
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
Tasks: TOP => db:migrate

ElasticBeanstalkでは、RDSの立ち上げはオプションなので eb create にDB作成するオプションを付けて自動で作成されるようにします。
※まだ実行しないでください。

eb create -db.engine postgres \
-db.user ebroot -db.pass testpassword

-db.engine postgres
DBのエンジンを決めます。mysql postgres など設定できます。

-db.user ebroot
DBのデフォルトユーザー名です。
お好きな名前を付けてください。デフォルトは ebroot です

-db.pass testpassword
DBのデフォルトユーザーのパスワードになります。
好きなパスワードを入れてください。

Elastic Beanstalkへデプロイ

eb createでアプリケーションを作成する

準備が整いました。eb createを実行しましょう。

以下のコマンドを丸々コピーして実行してください。
※オプションの値は適宜自分のものに変更してください。

~/eb_sample_app
eb create -db.engine postgres \
-db.user ebroot -db.pass testpassword \
--envvars RAILS_MASTER_KEY=da7xxxxxxxxxxxxxxxxxdf1b1de

実行すると4つの項目を聞かれます。

すべてデフォルトで良いので、空でエンターを4回押します。

Enter Environment Name
(default is eb-sample-app-dev): 

Enter DNS CNAME prefix
(default is eb-sample-app-dev): 

Select a load balancer type
1) classic
2) application
3) network
(default is 2): 

Would you like to enable Spot Fleet requests for this environment?
(y/N): 

するとなにやら実行されます。

EC2やらなんやら立ち上げていて、10分ほどかかるので2度目のコーヒーブレイクをお楽しみください。

Creating application version archive "app-f738-200316_020752".
Uploading eb_sample_app/app-f738-200316_020752.zip to S3. This may take a while.
Upload Complete.
An environment with that name already exists.
Enter Environment Name
(default is eb-sample-app-dev2): 
Environment details for: eb-sample-app-dev
  Application name: eb_sample_app
  Region: ap-northeast-1
  Deployed Version: app-f738-200316_020752
  Environment ID: 
  Platform: arn:aws:elasticbeanstalk:ap-northeast-1::platform/Puma with Ruby 2.6 running on 64bit Amazon Linux/2.11.3
  Tier: WebServer-Standard-1.0
  CNAME: 
  Updated: 2020-03-15 17:08:44.727000+00:00
Printing Status:
2020-03-15 17:08:42    INFO    createEnvironment is starting.
2020-03-15 17:08:44    INFO    Using elasticbeanstalk-ap-northeast-1-476091724641 as Amazon S3 storage bucket for environment data.
2020-03-15 17:09:06    INFO    Created target group named: arn:aws:elasticloadbalancing:ap-northeast-1:476091724641:targetgroup/awseb-AWSEB-1EYCMUPNKMX61/ccc7435548d76a87
2020-03-15 17:09:06    INFO    Created security group named: sg-09f51d827976f2e04
.
.
.

ログを見ると作成されてるのはこれらのようです。

  • Environment
  • Target Group
  • Security Group
  • Auto Scaling
  • RDS Database
  • Load Balancer
  • EC2 Instances
  • CloudWatch Alarm

時間がある方は、なにが作成されているのかチェックしてみてください。

時間がかかって ERROR: TimeoutError になった人は eb events -f を実行すれば再度確認できます。

ERROR: TimeoutError - The EB CLI timed out after 15 minute(s).
The operation might still be running. To keep viewing events,
run 'eb events -f'. To set timeout duration, use '--timeout MINUTES'.
$ eb events -f

Successfully が表示されれば完了です!

2020-03-15 17:14:00    INFO    Successfully launched environment: eb-sample-app-dev

デプロイしたアプリをひらく

eb open を実行すると作成したアプリを開くことができます。

実行してみましょう。

$ eb open

ブラウザでアプリが開けば成功です。

eb_product.jpg

実際にユーザーを作成して成功するか試してみましょう。

New Usernameを入力Create User

Successfullyと出れば成功です!

eb_product_user.jpg

トップに戻ると作成されていることが確認できます。

eb_product_users.jpg

AWS: ElasticBeanstalkを確認すると作成されたアプリケーションが確認できます。

緑色になっていると問題なく動作している状態です。
赤色などになっているとなにか問題が起きています。

作成したアプリケーションを開いてみましょう

eb_plat.jpg

作成したアプリケーションの情報が表示されます。

URLの部分が今回作成されたアプリケーションのURLです。

eb_plat2.jpg

サイドバーの設定を開くとインスタンスやロードバランサーの設定を確認することができます。

再度デプロイをする方法

ファイルを更新したら、commitしてeb deployをすれば
自動でファイルを転送されデプロイが完了します。

$ git add .
$ git commit -m "Add ..."
$ eb deploy

デプロイがこんなに簡単にできるなんて最高!!

SSH接続をする方法

EC2にSSH接続するには eb ssh を実行するとSSH接続できます。

$ eb ssh

Railsアプリは /var/app/current/ に入っているので、気になる人はチェックしてみてください。

[ec2-user@ip-172-31-1-95]
$ cd /var/app/current/
$ ls
Gemfile       Rakefile  config     lib           public   tmp
Gemfile.lock  app       config.ru  log           storage  vendor
README.md     bin       db         package.json  test

エラーで表示できなかった場合

エラーなどで表示できなかった方は、ログをチェックしてみてください。

$ eb logs

まとめ

今回はElastic Beanstalkを使って簡単にEC2にデプロイを試してみました。

VPC、RDSやオートスケール、ロードバランサーなど複雑な設定を知らなくても
スタンダードなインフラ設計で簡単につくれるElastic Beanstalkはとても良いと思いました。

ただ、VPCや中でなにを行われているのかわからないため、インフラを理解する観点として
同じ設計を手動で立てれるようになることも重要だと思います。

全体像を掴む方法として、まずはElastic Beanstalkを使ってみることをおすすめします!

次回の記事ではSSL化、RDSを独立させる方法、Dockerを使ったデプロイなどを解説していこうと思います。

参考にさせていただいたサイト

AWSのElastic Beanstalkでナウい構成のWebアプリを構築しよう! - Qiita
Rails5.2から追加された credentials.yml.enc のキホン - Qiita
Ruby on Railsの環境構築をElastic Beanstalkで行う - Qiita
RailsアプリケーションをElastic Beanstalkにデプロイするまで - Qiita
【2020年版】RailsアプリをElasticBeanstalkでデプロイするまでの手順 | ニートエンジニア
Elastic Beanstalk: can't find gem bundler (>= 0.a) - Stack Overflow
EB CLI コマンドリファレンス

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

AWS SAMを使ってLambdaにLayer設定する方法

AWS SAMを使ってLambdaにLayerを設定する方法

はじめに

AWS SAMでLambdaやdynamoDBを使ったサーバーレスアプリケーションを構築しているといろいろなプロジェクトで共通で使っているコードがでてきたりします。

共通化できるものはさっさと共通化して必要なときに呼び出しができるようになると便利なのでさっそく実践してみました。

AWS LambdaにはLayer機能があり、Lambdaのコードで使いたいライブラリなどを設定する事ができます。

Layerに依存関係のあるコードやライブラリをAWS SAMのプロジェクトとしてまとめて設定する方法を調べてやってみました。

Layerに必要なライブラリをZipでまとめてS3に保存して、AWSコンソールから設定する方法はいくつか見つけましたが、AWS SAMのプロジェクトとしてまとめてdeployする方法がなかなかうまくできなかったので備忘録として書いておきます。

前提条件

AWS SAM CLIのインストールはすでに済んでいるものとして説明します。

今回の説明ではPython3.6を使っています。

ディレクトリ構成

Layerとして使いたいコードやライブラリなどを格納するディレクトリは言語ごとに決められています。

AWS Lambda ライブラリの依存関係をレイヤーに含める

pythonならpythonpython/lib/python3.6/site-packagesに格納しておかないと下記のような参照エラーが表示されます。

Unable to import module 'app': cannot import name 'layer_code'

sam initコマンドで作成したSAMプロジェクトにLayerを格納するディレクトリを追加します。

sam-app
├── README.md
├── app
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── events
│   └── event.json
├── layer                       // ここにLayer格納用ディレクトリを追加
│   └── python                  // 言語名ディレクトリ
│       └── sample
│           └── sample_layer.py   // Layerとして使いたいコード
└── template.yaml

template.yamlの書き方

template.yaml
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: hello-world-sample-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.6
      Layers:
        - !Ref LayerSampleLayer     # ポイント1
      Events:
        (省略)

  # Layer定義
  LayerSampleLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: layer-sample-layer
      Description: Hello World Sample Application Resource Layer
      ContentUri: layer              # ポイント2

template.yamlの書き方のポイントは2つです。

  1. Lambda関数定義にLayersの項目を追加してLayer定義を参照するように設定する

  2. Layer定義ではLayerのコードを格納しているフォルダパスを設定する

    →正確には言語名ディレクトリの親ディレクトリを設定する

app.pyでのimport

template.yamlでLayer設定をしておくとapp.pyと同一階層にあるものとして使えるようになります。

ただ一度sam buildを行わないと設定は反映されません。

app.py
# レイヤーとして追加したファイルを参照する
from sample import sample_layer

def lambda_handler(event, context):
    res = sample_layer.hello()
    return res

sample_layer.py
def hello():
    return "hello Layer!"

参考

AWS サーバーレスアプリケーションモデル (AWS SAM) とは

AWS Lambda レイヤー

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

AWSで作ったLaravelアプリケーションをXserverにデプロイする

はじめに

タイトル通りですが、LaravelアプリケーションのデプロイをXserverで試してみました。

AWSでアプリケーションは作成したのですが、
XAMPPで作成した方も以下のやり方でできると思います。

まとめ

さっそくですがやり方を以下にまとめました。

※XAMPPでアプリケーションを作成した方は⑥以降を参考にしていただければと思います。

①AWSでLaravelアプリケーションを作成。

②作成したアプリケーションのフォルダをダウンロードしてXAMPP環境下(htdocs内)に保存する。

③XAMPPでphpMyAdminを起動。アプリケーション用のDB(DB名:laravel)を作成する。

④.envをXAMPP環境下でDBが使えるようにに編集。

.env
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

⑤コマンドプロンプトで同フォルダに移動してテーブル作成

$ cd 作成したアプリのフォルダ
$ php artisan migrate//このコマンドでテーブルが作成されます。

⑥FTP(FileZillaを使用しました)で転送したいドメイン内のpublic_htmlへ作成したアプリのpublic内のファイルをすべて転送

⑦publicフォルダ以外のファイルをまとめて新しく作ったフォルダに転送(public_htmlと同じ階層下)

フォルダ名はlaraveltestとしました。
コメント 2020-01-22 230052.png

⑧public_htmlに転送したindex.phpのrequire先を新しく作ったフォルダ先に変更

index.php
require __DIR__.'/../laraveltest/vendor/autoload.php';//28行目
~~
$app = require_once __DIR__.'/../laraveltest/bootstrap/app.php';//38行目

⑨XAMPP環境下のphpMyAdminで同アプリ用に作ったテーブルをエクスポート(.sqlファイル)

⑩先ほどエクスポートしたsqlファイルをXserver環境下のphpMyAdminにインポートしてテーブル作成

⑪転送した.envを以下のように編集して保存

.env
APP_URL=http://独自ドメイン/

DB_connection=mysql
DB_HOST=自身のサーバ名.xserver.jp
DB_PORT=3306
DB_DATABASE=自身のデータベース名
DB_USERNAME=自身のユーザー名
DB_PASSWORD=自身で設定したパスワード

⑫対象のドメインへアクセスして問題ないか確認。

実際にデプロイしたサイトは以下になります。
よければ覗いてみてください◎
https://craftbeers.site

参考

参考にさせていただいた記事は以下です。
https://qiita.com/hitotch/items/5c8c2858c883e69cd507

最後に

完全に個人的備忘録です。
もしかしたらかなり遠回りな方法でデプロイしているかもしれませんので、
他にも良い方法がありましたら教えていただけますと幸いです。
また、初投稿で書き方もあまりわからなかったのでかなり雑な記事になってしまいました。。。
 

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

Step Functionsを使ってみる

社会人一年目のtkg-70です。
誕生日記念に初投稿します。(嘘です、たまたまです。)
今回、業務でAWSのStep Functionsを使う機会がありまして、せっかくなので自分の勉強のためにも記事を書いてみようと思いました。
Step Functonsを使うにあたり、押さえたほうが良いところと躓いたところをまとめていこうと思います。(また、自分が使うときのためにも)

Step Functionsとは

うまく説明できる気がしないので、AWSの公式を貼っておきます。
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/welcome.html
ざっくり、作成した複数のLambdaを、一連の流れとして動かせるものって思ってます。

Step Functionsの作成

Step FunctionsはAmazon ステートメント言語を用い、JSONの形式でワークフローを書いていきます。
コードスニペットの生成のプルダウンにテンプレが一通り乗っており、モーダルで表示してくれるので、それをコピペして改変すれば書いていくことができます。
ワークフローを書き上げると、自動でビジュアルコンソールにフローチャートを作成してくれます。(自分で書いたワークフローが視覚的にわかるので、ありがたかったです。)

私が書いたコードはこんな感じ↓(LambdaのARNはテンプレのままなのでエラーでてます。)
2020-03-12_00h12_16.png

{
  "Comment": "A Hello World example of the Amazon States Language using Pass states",
  "StartAt": "LambdaA",
  "States": {
    "LambdaA": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Parameters": {
        "S3Bucket": "xxxxxx",
        "filePath": null
      },
      "OutputPath": "$",
      "Next": "eventParamChoice"
    },
    "eventParamChoice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.filePath",
          "StringGreaterThan": "0",
          "Next": "startShellProcess"
        },
        {
          "Not": {
            "Variable": "$.filePath",
            "StringGreaterThan": "0"
          },
          "Next": "FailedState"
        }
      ],
      "Default": "FailedState"
    },
    "startShellProcess": {
      "Type": "Parallel",
      "InputPath": "$",
      "OutputPath": "$",
      "Next": "SucessState",
      "Branches": [
        {
          "StartAt": "LambdaB",
          "States": {
            "LambdaB": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        },
        {
          "StartAt": "LambdaC",
          "States": {
            "LambdaC": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        },
        {
          "StartAt": "LambdaD",
          "States": {
            "LambdaD": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
              "InputPath": "$",
              "OutputPath": "$",
              "End": true
            }
          }
        }
      ]
    },
    "FailedState": {
      "Type": "Fail"
    },
    "SucessState": {
      "Type": "Pass",
      "End": true
    }
  }
}

Step Functionsの書き方

Step Functionsの一つの処理の書き方の例は下記の通りです。

"処理の名前": {
  "Type": "Task",
  "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
  "Parameters": {
    "S3Dir": "xxxxxxxxx",
    "filePath": null
  },
  "OutputPath": "$",
  "Next": "eventParamChoice",
  "Catch": [
    {
      "ErrorEquals": [
        "States.ALL"
      ],
      "Next": "FailedState"
    }
  ]
},

このソースに書いてあることの意味について説明していこうと思います。

Type

Typeの状態によってその処理でできることが変わります。上記のTaskの状態は、Lambdaを実行することができます。状態は他にもいくつかあります。
使用できるTypeの状態:

  • Choice(受け取った値から処理を分岐させることができる)
  • Wait(待機できる)
  • Succeed(実行を正常に停止する)
  • Fail(実行を失敗として停止する)
  • Parallel(複数のLambdaを並列実行できる、Parallelで書いた処理が一括りの処理として実行される)
  • Map(同じ処理に複数の値を渡し、動的に実行できる)

Resource

Task状態で使えるフィールドです。LambdaのARNを指定できます。

Parameters

Task状態で使えるフィールドです。Lambdaに渡す値をJSON形式で記述できます。
Lambdaに渡す値を記述できるのはParametersだけではありません。
実行時に、渡す値を記述し、InputPathのフィールドを使うことでLambdaに値を渡すこともできます。

OutputPath

Task状態で使えるフィールドです。次の処理に渡す値を指定できます。
例えば、上記の記述でLambdaに渡した値がそのまま帰ってきた場合、$.S3Bucketと指定すれば、次の処理に渡されるのは{"S3Bucket": "xxxxxx"}のみになります。

Next

次に実行する処理の名前を指定します。

Catch

Lambdaで起きたエラーをcatchするときに使うフィールドです。ここではStates.ALL(States.Runtimeを除くすべてのエラー)をcatchした時、FailedStateの処理に移ります。(書かなくても大丈夫です)

基本のTask状態の説明をしましたが、Typeと受け渡しをする値に気をつけておけば、他のTypeもフィールドを調べながら書いていくことができると思います。
Type状態の公式ガイドのリンク貼っときます。(これみれば、フィールドはだいたいわかる)
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-states.html

Step Functonsで躓いたこと

Step Functionsを使うにあたって、Parallel状態のエラー処理の記述に躓きました。どのように書くと、どのように実行されるかがわからなかったため、複数書いて実行してみました。

Parllel でのエラー処理を実行してみる

Step Functionsを用いたエラー処理の方法はいくつかあったため、全て試してみることにしました。

  • Lambdaでtry catchしない
    最初に特に何もエラー処理を書かずにStep Functionsを実行してみました。
    2020-03-15_17h20_11.png

    • エラーをなげるLambda
LambdaB
exports.handler = async (event) => {
    throw new Error
    return event;
};

ワークフローでLambdaBをクリックすることで、例外の欄にエラーが出ていることがわかります。

  • try catchした後、errorをthrowする
    次に、エラーをcatchしてthrowしています。
    2020-03-15_17h17_38.png

    • エラーをなげるLambda
LambdaB
exports.handler = async (event) => {
    try{
        throw new Error
        return event;
    }catch(err){
        throw err
    }
};

この方法もワークフローでLambdaBをクリックすることで、例外の欄にエラーが出ていることがわかります。この方法ならば、エラーにメッセージを加えたりできるのでしょうか・・・(やり方がわかりませんでした。)

  • try catchしてerrorをthrowしない
    2020-03-15_18h05_06.png

    • エラーをなげるLambda
LambdaB
exports.handler = async (event) => {
    try{
        throw new Error
        return event;
    }catch(err){
    }
};

Lambdaはエラーをだしていますが、Step Functionsのワークフローではエラーが表示されていません。(個別のCloud Watchログではエラーが表示されています。)

  • parallelでcatch(prallelにcatch追加し、Lambdaでエラーハンドリングは行わない) 2020-03-12_00h33_10.png
    • parallelに以下のコードを追加
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "FailedState"
        }
      ]

Parallelの出力とFailedStateの入力を見ることでerrorがわかります。この書き方では、Parallel内でエラーが発生した際のエラー処理をまとめて記述できます。



いろいろエラーの書き方を試してみましたが、どれがいいとかではなく、状況に応じて選択すべきだなと思いました。今回いろいろ実行してみて、注意した方がよいと思った点があるので、記載しておきます。

Parallelの注意ポイント

1.上記の「Parallelでcatch」は正常終了しているように見えますが・・・

2020-03-15_17h29_10.png

これを見ると、LambdaB、LambdaC、LambdaDが失敗しているように見えますが、LambdaC、LambdaDは正常終了しています。
→Parallelは記載した処理が一つのタスクとして失敗・成功が判断されます。Parallel内のタスクがエラーを出した場合、その時点で処理が終了しますが、呼び出されたLambdaは動いている点に注意が必要です。

2.parallelの出力は・・・

2020-03-12_00h59_52.png

Parallelに出力するLambdaが複数あるとParallelの出力がこうなってしまいました。
こうなると、parameterを操作するのが難しかったです。
Parllelの出力は操作しないか、どれか一つが出力するようにすべきかなと思います。

あとがき

初めてやりましたが、AWSはドキュメントがわかりやすくてよかったです。
Step Functionsは見た目でもわかりやすくて助かりました。
この記事が誰かの参考になればいいなと思います。
間違い等あればコメントください。

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

Dockerコンテナ上で動くGinサーバーにアクセスできないエラーの解決法

概要

GolangのWebフレームワークであるGinを使ったAPIサーバーを、Dockerコンテナ上にデプロイして動かそうとしたところ、APIにアクセスできずかなり長い時間悩まされました。

結果的には、Ginサーバーのコードの書き方の問題だったことが分かったのですが、解決方法を念のためここにメモしておきます。

ちなみに、このエラーはWindows10及びAWS上のUbuntuサーバー(t2.small)で起こりました(尤も、実行環境はこのエラーの発生にあまり関係ないようでしたが)。

状況

Ginを用いたAPIサーバーを立てようとしていました。まだ環境構築の段階なので、コードは以下のようなモックのものになっています。

package main

import (
    "log"
    "os"
    "github.com/gin-gonic/gin"
)

func main() {
    logConfig()

    r := gin.Default()
    r.GET("/accounting-api", func(c *gin.Context) {
        log.Println("GET")
        c.JSON(200, gin.H{
            "state": "success",
        })
    })
    r.DELETE("/accounting-api", func(c *gin.Context) {
        log.Println("DELETE")
        c.JSON(200, gin.H{
            "state": "success",
        })
    })
    log.Println("Start Server")
    r.Run()
}

func logConfig() {
    logFile, _ := os.OpenFile("log/log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    log.SetOutput(logFile)
    log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile)
    log.SetPrefix("[LOG] ")
}

単純に、GETDELETEメソッドで特定のパスへのリクエストが来たら、{"state": "success"}というjsonを返すだけのサーバーです。

そして、このサーバーを動かすためのDockerfileが以下です。

FROM golang:alpine
RUN apk update && apk add --no-cache git
RUN go get -u github.com/gin-gonic/gin && mkdir /usr/src && mkdir /usr/src/api
COPY ./api /usr/src/api
WORKDIR /usr/src/api
CMD ["go","run","main.go"]

ホスト上のapiというディレクトリには上記のGoファイル等があるため、それをコンテナ上にコピーして、サーバーを立ち上げます。このDockerfileをapiという名前でビルドして、それを以下のコマンドで立ち上げました。

docker run -p 8083:8080 api

ホスト上のポート8083をコンテナ上のポート8080にマッピングしています。上記のコマンドを実行すると、以下のような出力がなされ、Ginサーバーが立ち上がっていることが確認できます。

[GIN-debug] GET    /accounting-api           --> main.main.func1 (3 handlers)
[GIN-debug] DELETE /accounting-api           --> main.main.func4 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on localhost:8080

ポートを指定しなかったので、デフォルトでポート8080で動いています。このように一見ちゃんと動いているようにも見えますが、ホスト側でhttp:localhost:8083にcurlでアクセスしてみても、以下のようにエラーが出てしまいました。

curl: (52) Empty reply from server

エラー解消のため試みたこと

1. コンテナの中に入って、APIにアクセスできるか確認

まず最初に、コンテナ上で本当にGoのプログラムがちゃんと動いているのかを確認します。そのために立ち上げたdockerコンテナの中に入ってみます。

# apiサーバーの動くコンテナの中に入る
docker exec -it api /bin/ash

このコンテナのベースとなっているAlpineには/bin/bashがなかったので、/bin/ashを使います。そして以下のコマンドを打って、ちゃんとプログラムが動いているのか確かめます。

# そもそもcurlが入っていないので、インストールする
apk add --no-cache curl
# 念のためプロキシを無効にして、curlでapiサーバーにアクセスする
curl -x "" http://localhost:8080/accounting/api

curlの実行結果がこちら。

{"state": "success"}

ちゃんと結果が取れています。なので、コンテナ上ではちゃんとGinサーバーのプログラムが動いているようです。

2. DockerfileでポートをEXPOSE

コンテナ上ではプログラムは動いているようなので、次はポートマッピングの部分がうまくいっていないのではないかと疑ってみます。調べてみるとDockerfileにはEXPOSEというコマンドが書けるようなので、追加してみます。

EXPOSE 8080

これをやっても、解決しませんでした。

そもそも公式ドキュメントによると、EXPOSEコマンドは実際は何の働きもせず、特定のポートを開放する旨を開発者に知らせるための、ドキュメントのような役割しかもっていないようです。なので、EXPOSEコマンドをつけただけで問題が解決するはずがありませんでした。

3. Windowsのファイアウォールの設定の確認

開発は基本的にWindows上のDockerで行っていたため、Windowsのファイアウォールの設定を見直してみましたが、これも意味がありませんでした。

そもそも、このDockerfileをUbuntu上でビルドして立ち上げてみても、同様にAPIサーバーにアクセスできなかったため、最初からなんとなくWindowsのファイアウォールのせいではないことが分かっていましたが。

4. (これで解決)Ginサーバー側でポートの指定

Goのプログラムの中で、GInサーバーを立ち上げる部分でポートを指定するようにしたところ、上手くアクセスできるようになりました。具体的には、以下の部分です。

r := gin.Default()
r.Run(":8080")

何も指定しなくてもデフォルトで8080で立ち上がるため気にしていなかったのですが、しっかりと指定しないとどうやらダメなようです。

なぜポートはデフォルトではダメで、明示的に記さなければならないかは、よくわかりません。分かったら追記します。

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

AWSで設計する SAP NetWeaver 可用性と災害対策 その1

はじめに

基幹系や大規模なSORでもパブリッククラウドへの期待が高まっています。一方、全面クラウド移行するとなると移行コストやシステムの見直しでなかなかに困難が付きまとうという現状もあります。

このような場合、以下のような段階的なアプローチが有効です。

1.オンプレミスにて遠隔地へデータ複製&可用性を担保
              ↓
2.オンプレミスからパブリッククラウドへデータ複製&可用性を担保
              ↓
3.パブリッククラウドで全てデータ複製&可用性を担保

本稿では1.から2.に進むための技法としてベリタスを用いた方法について紹介します。

ミドルウェアの必要性

AWSは複数のアベイラビリティーゾーン(AZ)をまたいでデータのコピーを行うことにより、独自の可用性を提供します。しかし、AWSが提供するこれらの可用性はインスタンスの内側の障害を認識しません。つまりSAPアプリケーションの障害までカバーする可用性が必要な場合、インスタンス内に Veritas InfoScale のようなクラスタ化のためのミドルウェアが必要になります。

高可用性が必要なコンポーネント

SAPアプリケーションの可用性にとってクリティカルな次のコンポーネントは関連する単一障害点を排除し、高可用性を担保する必要があります。

データベースインスタンス
セントラルサービスインスタンス
エンキューレプリケーションサーバー

アプリケーションアベイラビリティの最適化

アプリケーションが停止した場合、AWSはインスタンスを再起動するか再デプロイする必要があります。これらの実施には多くの時間を要するため、目標復旧時間(RTO)を満たせない可能性があります。また、複数のSAPアプリケーションサーバーがシステム内に構成されている場合、一部の障害がシステム全体の停止に波及しないように、個別のオペレーター介入が必要になります。

お客様は通常、OS固有のカスタムスクリプトや監視ツールを使用してアプリケーションを監視します。InfoScaleは、SAPやOracle向けに開発された監視エージェントを使用する事で、そのような複雑な実装を簡素化します。インテリジェントなフェイルオーバー機能により、顧客は待機ノードの数を減らしてAWS内のインスタンス数を最適化できます。また、InfoScaleのディザスターリカバリー(DR)ソリューションは、AWSのリージョンをまたいで実装でき、本番業務に影響を与えないDRテストも提供します。

Veritas InfoScale

Veritas InfoScaleは主要なエンタープライズオペレーティングシステムとサーバー仮想化テクノロジーで動作します。InfoScaleをAWSインスタンス内にデプロイすることはAWS 環境におけるSAPの管理および保守をスムースにします。InfoScaleは、次のエージェントを使用してこれらのソリューションを提供します。

AWSIP
AWSRoute53
SAPNW
SAPComponents

Veritas InfoScaleの構成例

次の図はVeritas InfoScale を使用して作成されたさまざまな可用性のシステム構成を示しています。
image.png
図 1 – InfoScaleの構成タイプ

SAPエコシステム向けのInfoScaleの機能

InfoScaleは次のHAおよびDR機能によりSAPエコシステムの管理に適しています。

  • 監視やリカバリ自動化と障害時のアプリケーションのダウンタイムを最小化
  • オンプレミスからAWSへのフェールオーバーのサポート
  • 開発、テスト、本番が混在したフェイルオーバー構成による使用率の最適化
  • AWS上で稼働するSAPのコスト最適化、RPO/RTO要件の順守
  • 災害復旧(DR)のサポート

これには以下が含まれます。

 oオンプレミスからAWSへの切り替え
 o災害時に高可用性を達成するためのAWS向けの新しいエージェントのサポート
 oオンプレミスでサポートされているSAPエージェント

  オンプレミスと同様の実装方針
  AWSのインスタンス間でデータを共有するための独自のFSS
  AWSリージョンまたはオンプレミスからAWSへのVVR

Veritas InfoScaleのレプリケーション

VVRはVxVMの堅牢性、使いやすさ、高性能性やパフォーマンスの利点を享受するとともに現在のVxVMの構成をアプリケーションにほとんど影響なく透過的にレプリケーションします。VVRは、ソースボリュームに対するアプリケーションの書き込みを任意の距離にある1つ以上のリモートの拠点にレプリケーションします。リモートサイトではアプリケーションデータの一貫したコピーを提供し、本番サイトで災害が発生した場合は、レプリケーション済みデータを使ってビジネスアプリケーションを起動します。通常、本番拠点でアプリケーションが実行されているシステムはプライマリシステムと呼ばれ、レプリケーションターゲットのシステムはセカンダリシステム(もしくは待機系)と呼ばれます。プライマリシステムのボリュームは、セカンダリシステムのボリュームと最初に同期する必要があります。 VVRとネットワークを使用してプライマリロケーションとリモートロケーション間のアプリケーションデータを同期します。

Veritas InfoScale のフレキシブルストレージシェアリング

InfoScaleのフレキシブルストレージシェアリング(FSS)機能を使用すると、分散型の高可用性ファイルシステムを実現できます。FSSはパフォーマンスや可用性を犠牲にすることなく、ダイレクトアタッチストレージ(DAS)のポテンシャルを最大限に引き出します。このテクノロジーは従来のストレージエリアネットワーク(SAN)環境の20%未満のコスト最大4倍のパフォーマンスを発揮します。さらに、FSSはDASのみの環境に限定されません。AWSのEBSボリュームとの組み合わせでも使用することができます。詳細はVeritas FSS のデータシートを参照してください。

アベイラビリティゾーン間でFSSまたはVVRを使用する際の考慮事項

•AZ間でFSSまたはVVRを使用する場合は次の点を考慮してください。FSSはアプリケーションが両方のAZのデータに同時にアクセスできるアクティブ/アクティブなアプリケーションで使用できます。 VVRでは同期および非同期のレプリケーションが可能ですが、それぞれのAZからアサインしたEBSへのアクティブ/アクティブな同時アクセスはできません。

•FSSまたはVVRの同期レプリケーションを使用する場合、データを他のAZにミラーリングまたは同期する必要があるため、アプリケーションのスループットはネットワークの性能に依存します。

•ある程度のRPOを維持しながら、高いスループットが必要なアプリケーションの場合、ベリタスはVVRの非同期レプリケーションの使用を推奨します。

•従来のSANストレージ環境とは異なり、EBSボリュームなどのAmazonブロックストレージは一度に1つのEC2インスタンスにのみ接続できます。AWSでFSSを使用することにより複数のクラスター内のノードで実行されているアプリケーションに共有ストレージ環境を提供できます。VVRはAWSのリージョン間のレプリケーションに使用可能です。

AWS環境にFSSをデプロイしてより良いパフォーマンスを実現するためのベストプラクティスのいくつかは次のとおりです。

•インスタンスでAmazon Elastic Network Adapter(ENA)の拡張ネットワーキングを有効にしてFSSのデータ複製が低レイテンシになるようにスループットと秒間パケット数(PPS)を改善します。詳細はAWSのネットワーキングについてAmazonのドキュメントを参照してください。

•EBSに最適化されたインスタンスを選択して Amazon EBS のI/O とインスタンスからの別のネットワークトラフィックの競合を最小限に抑えてEBSボリュームの最高のパフォーマンスを実現します。EBSの最適化インスタンスの詳細については、Amazonのドキュメントを参照してください。

SAP NETWEAVER 対応のInfoScaleエージェント

InfoScale のエージェントは、OracleやSAPなどの代表的なエンタープライズアプリケーションのリソースを監視します。リソースのステータスを決定し、外部イベントに従ってそれらを開始または停止します。 SAP NetWeaver 対応のInfoScale エージェント(以降、SAPNWエージェントと記述)はクラスタ環境下でSAP NetWeaver の起動・停止・監視・リカバリーを行います。エージェントはSAPインスタンスをオンライン、モニター、オフラインにします。エージェントはシステムプロセスとサーバーの状態を監視し、予期せぬ障害が発生した場合にフェイルオーバーを行います。

SAP NetWeaverエージェントは次のSAPインスタンスタイプをサポートしています。

セントラルサービスインスタンス(ASCS)
プライマリアプリケーションサーバーと追加のアプリケーションサーバー
エンキューレプリケーションサーバーインスタンス(ERS)

エージェントは、次のタイプのSAPシステムをサポートします。

ABAP
Java
アドイン (ABAP + Java)

SAPライブラリおよびVERITASコネクタスクリプトによるInfoScaleとSAP NetWeaverの統合

SAP NetWeaverエージェントにより、InfoScale をSAP NetWeaver 7.xおよびSAP Kernel 7.20 DCKと統合できます。この目的で SAP のライブラリ(saphascriptco.so)とVeritas のクラスタコネクタスクリプト(sap_symc_cluster_connector)を使用します。この統合で SAP sapstartsrvコンポーネントはSAPインスタンスのステータス変更をInfoScaleへ通達することが可能となります。InfoScale との統合は SAP NetWeaver 7.xおよびSAP Kernel 7.20 DCK以降でのみサポートされます。

典型的な InfoScale のクラスターではSAP管理者がSAPクライアントを使用してSAPインスタンスのステータスを変更するとsapcontrolやstartsapなどの動作が発生します。管理者がSAPインスタンスを停止すると、InfoScale は障害を検出し、クリーン操作を実行します。また、管理者がSAPインスタンスを起動すると InfoScale はインスタンスがその制御外でオンラインになったことを検出します。

sapstartsrvとInfoScale 間の通信を有効にする必要があります。これにより、割り当てられたSAPインスタンスを開始または停止したときに、sapstartsrvがInfoScale に通知できるようになります。 これによりInfoScale はSAPインスタンスの正しいステータスを検出します。

InfoScale の AWS IPエージェント

InfoScale はAWS IPエージェントを提供します。これによりAWSの次のネットワークリソースを監視および管理できます。

Private IP
Private IPはネットワークデバイスが相互に通信するために使用するプライベートアドレスです。
Elastic IP
Elastic IPは、ダイナミッククラウドコンピューティング用に設計された静的IPv4アドレスであり、AWSアカウントに関連付けられています。
Overlay IP
AWSではトラフィックを、サブネットまたはAZが属している仮想プライベートネットワーク(VPC)のEC2インスタンスにリダイレクトできます。Overlay IPを使用するとクラスターノードが複数のサブネットまたはAZに分散している場合、クラスターノード間でIPアドレスをリダイレクトすることでフェールオーバーを実現します。

詳細については、InfoScaleのドキュメントを参照してください。

InfoScale の AWS Route53 エージェント

Amazon Route 53は可用性が高くスケーラブルなドメインネームシステム(DNS)サービスです。 InfoScale はホスト名とIPアドレス間のマッピングを更新および監視するAWS Route53エージェントを提供します。エージェントはサブネット間でノードをフェールオーバーするときにAmazon Route 53ドメインのマッピングを行います。

ホストゾーンを作成すると Amazon Route 53 はゾーンのネームサーバー(NS)レコードとStart of Authority(SOA)レコードを自動的に作成します。フェイルオーバー中にAmazon Route 53ドメインでリソースレコードを動的に追加および削除する必要がある場合は、AWS Route53エージェントを使用する必要があります。エージェントはフェールオーバー中に新しいリソースレコードマッピングでDNSを更新し、クライアントがアプリケーションのフェールオーバーインスタンスに接続できるようにします。なお、AWSRoute53エージェントを使用したくない場合は、DNSレコードを管理するためにレガシーDNSエージェントを使用できます。

おわりに

ちょっと長くなりましたが、いきなり濃いですね!次回はAWSで設計する可用性のユースケースをご紹介します。

商談のご相談はこちら

本稿からのお問合せをご記入の際には「お問合せ内容」に#GWCのタグを必ずご記入ください。ご記入いただきました内容はベリタスのプライバシーポリシーに従って管理されます。

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

社内 DeepRacerLeague の取り組みと縁の下から支えた超短期間での構築手法(後編)

こんにちは。
株式会社日立システムズ ビジネスクラウドサービス事業グループの藤巻です。

当社当事業部では昨年2019年9月より2020年3月にかけて、事業部内 DeepRacer League を開催していました。

前編はこちら

後編は限られた超短期間で実施した設計・構築についてお伝えします。

縁の下から支えた超短期間での構築手法

構築にあたっての調査・準備

話は戻って、2019年8月末です。
夏季休暇をはさみ、数度の打ち合わせでレギュレーションなどを突貫ですり合わせ、支払いの準備やルートアカウント用のメールアドレスの発行といった AWS の新規アカウント作成の準備も終わったころです。

社内 DeepRacer は2019年9月初週開始の予定だったので残された時間は、2 ~ 3 営業日しかありませんでした。
通常業務をこなしながら、自身の AWS 認定ダブル受験の追い込みや AWS 社主催のセミナー参加、プライベートでの LT 登壇の準備などがあり、正味1人日未満しか時間が取れない状況でした。

そんな中で、この取り組みに必要なものを洗い出しました。

  • AWS のアカウント
  • 参加者用 AWS アカウント
  • ログ管理
  • ユーザー作成、権限設計・設定
  • 利用料の通知方法の設計・開発

これらを早急かつ省力に済ませられる方法を編み出さないとなりませんでした。

構築開始

というわけで、 環境構築を始めました。
結果からお伝えすると、2019年9月3日の夜に構築し、9月4日朝に参加者への説明会で引き渡しとなりました。

構成図は以下のとおりです。
image.png

アカウントごとの簡単な説明です

  • 親アカウント
    • Payer アカウントのことであり、事務局用のマスターアカウント
    • 子アカウントのCloudTrailを集約する S3 バケット あり
    • 後述の利用料通知用の Amzon SNS と Lambda 関数を配置。Cost and Usage から取得
    • 子アカウントをサクッと作るための Lambda 関数も配置
    • 各チームメンバー用の IAM User を管理
    • CloudFormation StackSets の管理など
  • 子アカウント
    • 各チーム用のアカウント
    • CloudTrail の情報を 親アカウントの S3 バケット へ送信
    • DeepRacer の利用

ここからは以下の流れで構築・設定した内容についてお伝えします
現時点ではさらに便利な機能が実装されており、もっと省力に環境構築・設定が行えます。

  1. 親アカウントの開設
  2. 親カウントの初期設定
  3. 子アカウントの開設
  4. 子アカウントの初期設定
  5. 子アカウントユーザーのログイン

親アカウントの開設

通常の新規アカウントと同じ作り方でアカウントを作成します。

親アカウントの初期設定

CloudFormation StackSets の設定

以下の CloudFormation テンプレートを使って、CloudFormation StackSets を実行するアカウント(親アカウント)から、ほかのアカウント(子アカウント)にリソースを作成するための IAM ロール(AWSCloudFormationStackSetAdministrationRole)を作成します。

https://s3.amazonaws.com/cloudformation-StackSet-sample-templates-us-east-1/AWSCloudFormationStackSetAdministrationRole.yml

環境の設定

以下の CloudFormation テンプレートを使って環境設定を行います。

  • CloudTrail を格納する S3 バケットの作成
  • CloudTrail を格納する S3 バケットのポリシーの作成
  • CloudTrail の設定
  • IAM ユーザー用の共通ポリシーの作成

CloudFormation テンプレートを見るには開く
setupMasterEnv.yml
---
AWSTemplateFormatVersion: 2010-09-09

Resources:
  # S3 bucket for CloudTrail log
  S3BucketForCloudTrailLogs:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: '!your-s3-bucket-name!'
      AccessControl: "Private"
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
    DeletionPolicy: Delete

  S3BucketForCloudTrailLogsPolicy:
    Type: AWS::S3::BucketPolicy    
    DependsOn: S3BucketForCloudTrailLogs
    Properties:
      Bucket:
        Ref: "S3BucketForCloudTrailLogs"
      PolicyDocument:
        Statement:
          -
            Action: "s3:GetBucketAcl"
            Effect: "Allow"
            Resource:
              Fn::Join:
                - ""
                -
                  - "arn:aws:s3:::"
                  -
                    Ref: "S3BucketForCloudTrailLogs"
            Principal:
              Service: "cloudtrail.amazonaws.com"
          -
            Action: "s3:PutObject"
            Effect: "Allow"
            Resource:
              Fn::Join:
                - ""
                -
                  - "arn:aws:s3:::"
                  -
                    Ref: "S3BucketForCloudTrailLogs"      
                  - "/*"        
            Principal:
              Service: "cloudtrail.amazonaws.com"
            Condition:
              StringEquals:
                s3:x-amz-acl: "bucket-owner-full-control"

  # CloudTrail
  CloudTrailConf:
    Type: AWS::CloudTrail::Trail
    DependsOn: S3BucketForCloudTrailLogsPolicy
    Properties:
      TrailName: "default-trail"
      IsLogging: true
      IsMultiRegionTrail: true
      S3BucketName: "dracer-cloudtrail-logs"
      IncludeGlobalServiceEvents: true
      EnableLogFileValidation: true

  # IAM Policy
  IAMPolicyCommon:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: "pol-Common"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Action:
              - "sts:DecodeAuthorizationMessage"
              - "sts:GetCallerIdentity"
              #- "sts:GetSessionToken"
            Effect: "Allow"
            Resource: "*"
          -
            Action:
              - "iam:Get*"
              - "iam:List*"
              - "iam:ChangePassword"
              - "iam:*MFA*"
            Effect: "Allow"
            Resource: "*"

課金情報通知

無尽蔵に予算があるわけではないので、各チームに対して利用料を通知するための Lambda 関数も整備しました。

使用しているのは、 Amazon SNS, Cost and Usage Report です。
Amazon SNS については、後続の子アカウントの初期設定の中で行っています。
この Lambda 関数は AWS Lambda の Python 3.6 で実装・動作確認をしております。

Lambda 関数を見るには開く
getCostReport
import datetime
import json
import os
import boto3
from decimal import *
from dateutil.relativedelta import relativedelta

# Set account_id and region
my_account = str(boto3.client('sts').get_caller_identity()['Account'])
my_region = str(os.getenv("AWS_DEFAULT_REGION"))

# Set sns_topic_arn_base
topic_arn_base = 'arn:aws:sns:'+ my_region + ':'+ my_account + ':'

# Set Timezone
JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')

# Get AWS Organizations Account List
accounts = boto3.client('organizations').list_accounts()['Accounts']
budget = os.environ['budget']

# AWSアカウントIDとそのチーム名を定義
# メールにチーム名を出さないのであればこの定義は不要
teamMap={
    "AccoudId":"TeamsName",
    "AccoudId":"TeamsName"
}

def lambda_handler(event, context):

    today = datetime.datetime.today()
    client = boto3.client('ce', my_region)

    # 1回で使用料をとっちゃう(API利用料削減)
    cau = client.get_cost_and_usage(TimePeriod={'Start': '{0}-{1:02}-01'.format(today.year, today.month),
                                                'End': '{0}-{1:02}-{2:02}'.format(today.year, today.month, today.day)},
                                    Granularity='MONTHLY', Metrics=['UnblendedCost'],
                                    GroupBy=[
                                        {
                                            'Type': 'DIMENSION',
                                            'Key': 'LINKED_ACCOUNT'
                                        }
                                    ])

    # アカウント毎に処理
    for result in cau['ResultsByTime']:
        for group in result['Groups']:
            msg = []
            accountId = str(group['Keys'][0])
            topic_arn = topic_arn_base + accountId
            teamName = teamMap.get(accountId)
            subject=('使用料のお知らせ({0})'.format(teamName))
            #subject=('使用料のお知らせ({0})'.format(accountId))            
            msg.append('チーム「 {0} 」各位\n\n本日までの使用料をお伝えします。\n'.format(teamName))
            #msg.append('チーム「 {0} 」各位\n\n本日までの使用料をお伝えします。\n'.format(accountId))

            msg.append(' {0} / {2} ({1}) 使用率:{3} %'.format(round(float(group['Metrics']['UnblendedCost']['Amount']),3),
                                                 group['Metrics']['UnblendedCost']['Unit'],
                                                 budget,
                                                 round((round((float(group['Metrics']['UnblendedCost']['Amount'])),3) / float(budget)),3)*100
                                                 ))
            msg.append('--------------------------------------------\n')

            #print(topic_arn)
            #print(subject)
            #print(msg)

            # SNS publish
            client = boto3.client('sns')
            request = {
               'TopicArn': topic_arn,
               'Message': '\r\n'.join(msg),
               'Subject': subject
            }

            response = client.publish(**request)

  • 環境変数
    • budget:レギュレーションで定めた毎月の予算額。ドルで指定

この Lambda 関数は CloudWatch Events で月曜日から金曜日の毎朝9時に実行するようにしました。
image.png

子アカウントの開設

親アカウントに作成した以下の Lambda 関数にアカウント名とルートアカウント用メールアドレスを環境変数に設定して実行します。
この Lambda 関数は AWS Lambda の Python 3.6 で実装・動作確認をしております。

Lambda 関数を見るには開く
createAccount
import boto3
import os
def lambda_handler(event, context):
    mail = os.environ['email']
    accountName = os.environ['accountName']
    roleName =  'default-role-by-organizations'

    org = boto3.client('organizations')
    response = org.create_account(
        Email=mail,
        AccountName=accountName,
        RoleName=roleName,
        IamUserAccessToBilling='ALLOW'
    )
    print(response)

  • 環境変数
    • email:ルートアカウント用メールアドレス
    • accountName:アカウントの名前

実行結果として、12桁のアカウント ID が返ってくるので控えておきます。
※AWS Organizations の画面からも確認可能です。

子アカウントの初期設定

ルートアカウントのパスワード設定

このままでは、ルートアカウントのパスワードが分からない状態になっています。
このドキュメントを参考にして、パスワードの設定を行います。

CloudFormation StackSets の設定

以下の CloudFormation テンプレートを使って、CloudFormation StackSets を実行するアカウント(親アカウント)からのリクエストを受け付けるための IAM ロール(AWSCloudFormationStackSetExecutionRole)を子アカウントに作成します。

本テンプレートは、親アカウントのアカウントID(12桁の数字)を指定して実行する必要があります。

https://s3.amazonaws.com/cloudformation-StackSet-templates-us-east-1/managed_account_role.template

CloudFormation StackSets を利用した 子アカウント側の設定

※親アカウント側で実施します。
※この流れを1回実施後に、子アカウントが増えた際には、次の項から実行します。

1. CloudFormation の画面を表示し、StackSets を選択します
image.png

2.StackSet の作成ボタンをクリックします
image.png

3.テンプレートファイルのアップロードを行い、次へをクリックします
image.png

アップロードするのは以下の内容の CloudFormation テンプレートです。

CloudFormation テンプレート見るには開く
setup_target_stack.yml
---
AWSTemplateFormatVersion: 2010-09-09

Parameters:
    masterAccountId:
        Type:                 String

Resources:
  AdminIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "admin_role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Principal:
              AWS: !Sub "arn:aws:iam::${masterAccountId}:root"

  AdminIAMPolicy:
    DependsOn: AdminIAMRole
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: "admin"
      PolicyDocument:
        Statement:
          -
            Effect: "Allow"
            Action:
              - "*"
            Resource: "*"
      Roles:
        - "admin_role"

  CloudTrailConf:
    Type: AWS::CloudTrail::Trail
    Properties:
      TrailName: "default-trail"
      IsLogging: true
      IsMultiRegionTrail: true
      S3BucketName: "dracer-cloudtrail-logs"
      IncludeGlobalServiceEvents: true
      EnableLogFileValidation: true

4.StackSet の設定を行います
StackSet 名や、パラメーターの指定を行い、次へをクリックします。
image.png

5.アクセス許可の設定を行い、次へをクリックします。
「セルフサービスのアクセス許可」を選択し、AWSCloudFormationStackSetAdministrationRole を選択しました。
image.png

補足:
本環境は AWS Organizations を利用しているので「サービスマネージドアクセス許可」のほうが簡単です。
しかし、この機能がリリースされたのは2020年2月半ばのため、2019年9月時点では使用できませんでした。
本稿を参考にしてAWS Organizations を利用環境で構築される方は、「サービスマネージドアクセス許可」をお勧めします。これまでお伝えした IAM ロールの事前設定なども不要になります。

6.デプロイ先を指定します
「スタックをアカウントにデプロイ」を選択し、デプロイ先の子アカウントのID(12桁の番号)を指定します。複数ある場合は、カンマ区切りで指定します。
image.png

7.デプロイ先のリージョンを指定します
今回は DeepRacer を使うことがメインなので、 バージニアリージョン(米国北東部:us-east-1)を指定しました。
image.png

8.デプロイオプションを指定し「次へ」をクリックします
デプロイ先のアカウントが複数ある場合は、同時アカウントの最大数を増やすことで速く終わります。
image.png

9.レビュー画面で問題がなさそうであれば(適宜チェックボックスにチェックを入れて)「送信」ボタンをクリックします

これで、指定したアカウントに対して設定が行われます。

CloudFormation StackSets を利用して 新たに追加された子アカウント側の設定

1.前項で作成した StackSet を選択して[アクション]>[StackSet に新しいスタックを追加]をクリックします。
image.png
image.png

2.あとは、前項の 6 から 9 と同様の操作を行い、[次へ]をクリックします
image.png

3.レビュー画面で問題がなさそうであれば(適宜チェックボックスにチェックを入れて)「送信」ボタンをクリックします

子アカウント用の設定

※親アカウント側で実施します。
以下の CloudFormation テンプレートを適宜編集し、親アカウント側の「スタック」として実行します。
子アカウントの数だけスタックを作成・実行します。

CloudFormation テンプレート見るには開く
setup_Template.yml
---
AWSTemplateFormatVersion: 2010-09-09

Metadata:
    "AWS::CloudFormation::Interface":
        ParameterGroups:
            - Label:
                  default: "AccountId"
              Parameters:
                  - AccountId

            - Lable:
                defaut: "Team Id"
              Parameters:
                  - TeamId

            - Label:
                  default: "UserId"
              Parameters:
                  - userN

Parameters:
    AccountId:
        Type:                 String
        Default:              ''

    TeamId:
        Type:                 String
        Default:              'dracer_N'
    userN:
        Type:                 String
        Default:              ''

Resources:
  # IAM
  # IAM Policy
  IAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub "pol-${TeamId}"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Action:
              - "sts:AssumeRole"
            Effect: "Allow"
            Resource:
              !Sub "arn:aws:iam::${AccountId}:role/admin_role"
          -
            Action:
              - "iam:ChangePassword"
              - "iam:*LogionProfile"
              - "iam:*AccessKey*"
            Effect: "Allow"
            Resource:
              Fn::Join:
                - ""
                - 
                  -
                    !Sub "arn:aws:iam::${AccountId}:user/"
                  - "${aws:username}"  

  # IAM Group
  IAMGroup:
    DependsOn: IAMPolicy
    Type: AWS::IAM::Group
    Properties: 
      GroupName: !Sub ${TeamId}
      ManagedPolicyArns:
        -
          Fn::Join:
            - ""
            -
              - "arn:aws:iam::"
              - 
                !Ref AWS::AccountId
              - ":policy/pol-Common"             
        - 
          Fn::Join:
            - ""
            - 
              - "arn:aws:iam::"
              - 
                !Ref AWS::AccountId
              - ":policy/pol-"
              -
                !Sub  ${TeamId}

  # IAM User 人数分増やし適宜編集
  IAMUserN:
    DependsOn: IAMGroup
    Type: AWS::IAM::User
    Properties:
      UserName: !Sub ${userN}
      Groups:
        - !Sub ${TeamId}
      LoginProfile:
        Password: "P@ssw0rd"
        PasswordResetRequired: true

  # SNS Topic
  SNSTeams:
    Type: AWS::SNS::Topic
    Properties: 
      DisplayName: !Sub ${AccountId}
      TopicName: !Sub ${AccountId}
      Subscription: 
        - Endpoint: !Sub "${TeamId}@example.com"
          Protocol: "email"

この CloudFormation テンプレートを実行すると、SNSTeams の Endpoint で指定したメールアドレスに対して以下のようなメールが届きますので、Confirm subscriptionリンクをクリックします。
image.png

すると、ブラウザが開き以下のような画面が表示されるので設定が完了します。

image.png

子アカウントユーザーのログイン方法

マネージメントコンソールのスイッチロール機能を利用してログインを行います。
※2020年3月時点での画面構成です。 AWS 社の仕様変更により画面の内容や構成が変わることがあります。

  1. AWSへアクセスする https://dracer-hisys-mng.signin.aws.amazon.com/console
  2. 各自に連絡したユーザーIDとパスワードでログインし、初回はパスワードの設定を行います
  3. 以下の流れでチームごとのコンソールに移行します image.png 4.ロールの切り替え image.png 5.アカウントIDやロール、表示名の指定を行い[ロール切り替え」ボタンをクリックします image.png 6.切り替え完了 image.png 1度設定したら、「ロール履歴」にある、表示をクリックするだけでOKです

まとめ・振り返り

実質1人日未満という限られた時間の中で、多数の環境を省力かつ迅速に構築することができました。
これもクラウドを使ううえでの利点であると感じています。
今回は、AWS DeepRacer を題材としていましたが、例えばハッカソン用などさまざまな活動に横展開を行い活用ができそうです。

登壇資料作成やブログ化にあたり、既存の設定や定義、画面などを見直してみました。
その際、機能拡充が行われていることがわかったので、次回以降の活動ではそのあたりも盛り込み、最適化を図っていきたいです。

日立システムズについて

https://jawsdays2020.jaws-ug.jp/supporter/%e6%a0%aa%e5%bc%8f%e4%bc%9a%e7%a4%be%e6%97%a5%e7%ab%8b%e3%82%b7%e3%82%b9%e3%83%86%e3%83%a0%e3%82%ba/

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

PythonでAmazon SESからメールを送ってみた

きっかけ

定期的に社内の複数宛先にメールを一斉送信する必要があり、今まではスプレッドシート+GASでGmailを平和に送っていた。
が!社内のメール送信システムに仕様変更が発生!(誤送信防止システム)
結果、スプレッドシート+GASのGmailが時々うまく動かなくなる :sob: うわぁぁぁぁん
Gmailはダメだ、何か別のメールを・・・・よし、Amazon SES にしよう!

PythonでAmazon SESからのメール送信実験

まずは Boto3の公式ドキュメントSES をチェック。
ふむふむ、send_email(**kwargs)send_raw_email(**kwargs) を使ったら良さそう。
違いは send_raw_email(**kwargs) だとヘッダー指定やファイル添付が出来る様子。
逆に send_email(**kwargs) はその辺に対応していない代わりにシンプルと。
ファイル添付はしたいけれど今回はさくっと実装してメールを送れる状態にさっさとしたいから send_email(**kwargs) でいこう。

まずBoto3でsesを読み込んで

import boto3

client = boto3.client('ses', 
        aws_access_key_id = accesskey, 
        aws_secret_access_key = secretkey, 
        region_name = region
    )

send_email(**kwargs) で送信

client.send_email(
        Source = 'sender@example.com',
        Destination = {
            'ToAddresses': [
                'recipient@example.com',
            ]
        },
        Message = {
            'Subject': {
                'Data': 'テストメール',
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': 'テスト本文',
                    'Charset': 'UTF-8'
                }
            }
        }
    )

よし、基本はOK。次は実際に使えるものに書き換えていきます。
注意:新規で作ったAmazon SESは機能が制限されており、使用するメールアドレスを事前に登録しておく必要があります。
詳しくは公式のAmazon SES に関してよくある質問Q: Amazon SES サンドボックスとは何ですか? を確認してみてください。

本番実装

send_email(**kwargs) でうまく送信出来ることが確認できたので本番で使えるように実装していきます。
まず最初にAWSの認証情報をコード内に埋め込むのは嫌なのでCSVファイル(IAMからダウンロードするファイル)から読み取るようにします。

import csv

def get_credentials():
    with open('認証情報のCSVファイル') as f:
        credentials_file = csv.reader(f)
        # AWSの認証情報のCSVファイルの1行目が項目名なので2行目を読み出します
        next(credentials_file)
        for credentials_array in credentials_file:
            return credentials_array

次にメールの内容は更新がしやすい方がいいと思うので外部テキストファイルから読み取るようにします。
メールタイトルとメール本文を分けたいのでテキストファイル内に -----ここからメール本文----- と適当な目標をつけることにします。
メール内容ファイルのイメージ

テストメール件名
-----ここからメール本文-----
テストメール本文。
def get_mail_content():
    with open('メール内容のテキストファイル') as f:
        mail_content_all = f.read()
    mail_content_splitted = mail_content_all.split('\n-----ここからメール本文-----\n')
    return mail_content_splitted

宛先のメールアドレスも更新がしやすいように外部CSVファイルから読み取るようにします。

def get_addresses():
    with open('メールアドレスのCSVファイル') as f:
        addresses_file = csv.reader(f)
        for addresses_array in addresses_file:
            return addresses_array

これでメールに必要な情報が揃ったので、作った関数から必要な情報を集めて send_email(**kwargs) で送信。

# AWSの認証情報取得
access_key = get_credentials()[0]
secret_key = get_credentials()[1]

client = boto3.client('ses',
    aws_access_key_id = access_key,
    aws_secret_access_key = secret_key,
    region_name = 'Amazon SESで利用しているリージョン (まだ日本はない)'
)

# メール内容の取得
mail_title = get_mail_content()[0]
mail_body = get_mail_content()[1]

# メール宛先の取得
to_addresses = get_addresses()
# メールアドレスは配列で渡す必要があるので [ ] で囲む
cc_addresses = ['CCの宛先メールアドレス']

# メール送信
client.send_email(
    Source = '送信元メールアドレス',
    Destination = {
        'ToAddresses': to_addresses,
        'CcAddresses': cc_addresses
    },
    Message = {
        'Subject': {
            'Data': mail_title,
            'Charset': 'UTF-8'
        },
        'Body': {
            'Text': {
                'Data': mail_body,
                'Charset': 'UTF-8'
            }
        }
    }
)

これでOK!・・・と思いましたが、公式の Amazon SES に関してよくある質問 にこんな説明が

Q: 1 つの E メールメッセージで指定できる受信者の数には制限がありますか?

Amazon SES を使用して送信するそれぞれのメッセージに対して、最大 50 人の受信者を指定できます。「To:」、「CC:」、「BCC:」フィールドのすべてのアドレスがこの制限に含まれます。50 人以上の受信者に E メールメッセージを送信する場合は、受信者リストを 50 人以下のグループに分割し、各グループに分けてメッセージを送信する必要があります。

ちなみに50人を超えた場合は以下のエラーメッセージが返されるようです。

botocore.exceptions.ClientError: An error occurred (InvalidParameterValue) when calling the SendEmail operation: Recipient count exceeds 50.

50人送信対応

なるほど。
一度に送る宛先を50人以内にする処理が必要ですね。
まずメール送信部分も関数にします。

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body):
    # メール送信処理
    client.send_email(
        Source = '送信元メールアドレス',
        Destination = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        },
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

このsend_emailを宛先50人ごとに呼び出せば

cc_addresses = ['CCの宛先メールアドレス']
to_addresses = get_addresses()
to_addresses_splitted = []
# 50毎のセット数をloop_countに代入
loop_count = len(to_addresses) // 50 + 1
for i in range(loop_count):
    # セット内の50件(CCメールアドレス含む)を一つずつ取得
    for j in range(50 - len(cc_addresses)):
        # send_emailに渡すメールアドレス一覧のto_addresses_splittedに順番にメールアドレスを入れていく
        try:
            to_addresses_splitted.append(to_addresses[i * 50 + j])
        # 存在しないインデックスを指定した時の例外処理
        except:
            pass
    send_email(client, to_addresses_splitted, cc_addresses, mail_title, mail_body)
    # セット毎にクリアする
    to_addresses_splitted = []

おっと、このままだと分割したメール全てがCCメールアドレスに送られてしまいますね。
メール送信としては1回でCCメールアドレスにも1通しか送りたくないので、分割送信の1回目だけCCメールアドレスに送るようにします。

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body, i):
    # 分割1回目だけCCアドレスに送る
    if 0 == i:
        destination_value = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        }
    else:
        destination_value = {
            'ToAddresses': to_addresses
        }

    # メール送信処理
    client.send_email(
        Source = '送信元メールアドレス',
        Destination = destination_value,
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

まとめ

import boto3
import csv

def main():
    # AWS情報
    access_key = get_credentials()[0]
    secret_key = get_credentials()[1]

    client = boto3.client('ses',
        aws_access_key_id = access_key,
        aws_secret_access_key = secret_key,
        region_name = 'us-east-1'
    )

    # メール内容の取得
    mail_title = get_mail_content()[0]
    mail_body = get_mail_content()[1]

    # メール宛先の取得
    cc_addresses = ['CCの宛先メールアドレス']
    to_addresses = get_addresses()
    to_addresses_splitted = []
    # 50毎のセット数をloop_countに代入
    loop_count = len(to_addresses) // 50 + 1
    for i in range(loop_count):
        # セット内の50件(CCメールアドレス含む)を一つずつ取得
        for j in range(50 - len(cc_addresses)):
            # send_emailに渡すメールアドレス一覧のto_addresses_splittedに順番にメールアドレスを入れていく
            try:
                to_addresses_splitted.append(to_addresses[i * 50 + j])
            # 存在しないインデックスを指定した時の例外処理
            except:
                pass
        send_email(client, to_addresses_splitted, cc_addresses, mail_title, mail_body, i)
        # セット毎にクリアする
        to_addresses_splitted = []

def get_credentials():
    with open('credentials.csv') as f:
        credentials_file = csv.reader(f)
        # AWSの認証情報のCSVファイルの1行目が項目名なので2行目を読み出します
        next(credentials_file)
        for credentials_array in credentials_file:
            return credentials_array

def get_addresses():
    with open('addresses.csv') as f:
        addresses_file = csv.reader(f)
        for addresses_array in addresses_file:
            return addresses_array

def get_mail_content():
    with open('mail_content.txt') as f:
        mail_content_all = f.read()
    mail_content_splitted = mail_content_all.split('\n---------------------ここからメール本文---------------------\n')
    return mail_content_splitted

def send_email(client, to_addresses, cc_addresses, mail_title, mail_body, i):
    # 分割1回目だけCCアドレスに送る
    if 0 == i:
        destination_value = {
            'ToAddresses': to_addresses,
            'CcAddresses': cc_addresses
        }
    else:
        destination_value = {
            'ToAddresses': to_addresses
        }

    # メール送信処理
    client.send_email(
        Source = '送信元メールアドレス',
        Destination = destination_value,
        Message = {
            'Subject': {
                'Data': mail_title,
                'Charset': 'UTF-8'
            },
            'Body': {
                'Text': {
                    'Data': mail_body,
                    'Charset': 'UTF-8'
                }
            }
        }
    )

if __name__ == "__main__":
    main()

これでローカルから心配なくメール送信が出来るようになりました。
ただローカルからだと他の人が送れない(=メール送信担当になってしまう)のでそのうちAWS Lambda化かなー。

We're hiring!

AIチャットボットを開発しています。
ご興味ある方は Wantedlyページ からお気軽にご連絡ください!

参考記事

Boto3の公式ドキュメント
Amazon SES に関してよくある質問

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

Amazon LinuxにKubernetes環境構築【kubeadm】

前提

EC2でAmazonLinuxインスタンスを作成してあること(CPUとメモリは最低でも2以上を要求されるのでt2.microではダメ)

背景

当たり前のようにkubernete環境構築で詰まったので手順をメモ

環境

2020/3月時点でのバージョン

[root@kube-master work]# kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.4", GitCommit:"8d8aa39598534325ad77120c120a22b3a990b5ea", GitTreeState:"clean", BuildDate:"2020-03-12T21:03:42Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.4", GitCommit:"8d8aa39598534325ad77120c120a22b3a990b5ea", GitTreeState:"clean", BuildDate:"2020-03-12T20:55:23Z", GoVersion:"go1.13.8", Compiler:"gc", Platform:"linux/amd64"}

事前準備

  1. host名の設定/hostsファイル編集
  2. Dockerのインストール
  3. swapの無効化
  4. SELinuxを止める(本番環境ではやらないほうがいいらしい)
  5. iptables編集
  6. Dockerのkubelet向けcgroup設定

host名の設定

# いちおうrootユーザーになっとく
[ec2-user@ip-10-0-10-176 ~]$ sudo su -
[root@kube-master ~]# hostnamectl set-hostname kube-master
[root@kube-master ~]# echo "[your aws ec2 public ipaddress] kube-master" >> /etc/hosts
[root@kube-master ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost6 localhost6.localdomain6
3.112.45.181 kube-master

Dockerインストール

[root@kube-master ~]# yum update -y
[root@kube-master ~]# yum install -y docker
[root@kube-master ~]# systemctl enable docker && systemctl start docker

インストールの確認

[root@kube-master ~]# docker info | grep -i version
Server Version: 18.09.9-ce
containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb
runc version: 2b18fe1d885ee5083ef9f0838fee39b62d653e30
init version: fec3683
Kernel Version: 4.14.171-136.231.amzn2.x86_64

[root@kube-master ~]# docker info | grep -i driver
Storage Driver: overlay2
Logging Driver: json-file
Cgroup Driver: cgroupfs # あとでここ変える

swapの無効化

swap無効化し、swap領域が使われていないことを確認する。

公式サイトにswap無効化してくださいと書かれている(Swap disabled. You MUST disable swap in order for the kubelet to work properly.)

[root@kube-master ~]# swapoff -a
[ec2-user@ip-10-0-10-176 ~]$ free
              total        used        free      shared  buff/cache   available
Mem:        8166360      224408     7181288         484      760664     7695804
Swap:             0           0           0

SELinuxを止める

以下の理由により

Setting SELinux in permissive mode by running setenforce 0 and sed ... effectively disables it. This is required to allow containers to access the host filesystem, which is needed by pod networks for example. You have to do this until SELinux support is improved in the kubelet.

[ec2-user@ip-10-0-10-176 ~]$ setenforce 0
setenforce: SELinux is disabled
[root@kube-master ~]# sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

sysctlでネットワークをブリッジできるようする

Some users on RHEL/CentOS 7 have reported issues with traffic being routed incorrectly due to iptables being bypassed. You should ensure net.bridge.bridge-nf-call-iptables is set to 1 in your sysctl config, e.g.

[root@kube-master ~]# cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
[root@kube-master ~]# sysctl --system

Dockerのkubelet向けcgroup設定

DockerのcgroupDriverをsystemdに設定する

[root@kube-master ~]# cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}
EOF
[root@kube-master ~]# mkdir -p /etc/systemd/system/docker.service.d
[root@kube-master ~]# systemctl daemon-reload
[root@kube-master ~]# systemctl restart docker
[root@kube-master ~]# docker info | grep -i driver
Storage Driver: overlay2
Logging Driver: json-file
Cgroup Driver: systemd

kubernetesインストール

  1. Kubernetesのリポジトリ登録
  2. kubelet/kubeadm/kubectlインストール
  3. kubeadm init
  4. CNI構築
  5. all-in-one化

Kubernetesのリポジトリ登録

公式のものを参考に

[root@kube-master ~]# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

kubelet/kubeadm/kubectlインストール

[root@kube-master ~]# yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
[root@kube-master ~]# systemctl enable kubelet && systemctl start kubelet
Created symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /usr/lib/systemd/system/kubelet.service.

kubeadm実行

公式サイトを参考に実行していく
CNIにはCalicoを選択

[root@kube-master ~]# kubeadm init --pod-network-cidr=192.168.0.0/16
[root@kube-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.8/manifests/calico.yaml

initが成功したらkubeadmの指示に従う

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

[root@kube-master ~]# mkdir -p $HOME/.kube
[root@kube-master ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@kube-master ~]# chown $(id -u):$(id -g) $HOME/.kube/config

kubeadm join --token ~~はall-in-one構成では必要ないらしいのでやらない

calicoネットワーク構築

[root@kube-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.8/manifests/calico.yaml

kubernetesのall-in-one構築

kubectl taint nodes --all node-role.kubernetes.io/master-

動作確認

[root@kube-master ~]# kubectl get all --all-namespaces
NAMESPACE     NAME                                           READY   STATUS    RESTARTS   AGE
kube-system   pod/calico-kube-controllers-5c45f5bd9f-74f4h   1/1     Running   0          4m32s
kube-system   pod/calico-node-mmvqh                          1/1     Running   0          4m33s
kube-system   pod/coredns-6955765f44-45mkb                   1/1     Running   0          16m
kube-system   pod/coredns-6955765f44-xntv5                   1/1     Running   0          16m
kube-system   pod/etcd-kube-master                           1/1     Running   0          16m
kube-system   pod/kube-apiserver-kube-master                 1/1     Running   0          16m
kube-system   pod/kube-controller-manager-kube-master        1/1     Running   0          16m
kube-system   pod/kube-proxy-d2qmc                           1/1     Running   0          16m
kube-system   pod/kube-scheduler-kube-master                 1/1     Running   0          16m

NAMESPACE     NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP                  16m
kube-system   service/kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   16m

NAMESPACE     NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                 AGE
kube-system   daemonset.apps/calico-node   1         1         1       1            1           beta.kubernetes.io/os=linux   4m34s
kube-system   daemonset.apps/kube-proxy    1         1         1       1            1           beta.kubernetes.io/os=linux   16m

NAMESPACE     NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
kube-system   deployment.apps/calico-kube-controllers   1/1     1            1           4m34s
kube-system   deployment.apps/coredns                   2/2     2            2           16m

NAMESPACE     NAME                                                 DESIRED   CURRENT   READY   AGE
kube-system   replicaset.apps/calico-kube-controllers-5c45f5bd9f   1         1         1       4m34s
kube-system   replicaset.apps/coredns-6955765f44                   2         2         2       16m

参考文献

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

『実践Terraform』を写経して完全に理解した時の学習メモ

「実践Terraform」で何を学んだか

本書ではAWS上にアプリケーションを構築するためにTerraformを使い、それを通してTerraformを学んでいくという構成になっている。

アーキテクチャの全体像をした上で、そのアーキテクチャの構築をTerraformを使って行なっていく。

本書は「入門編」「実践編」「運用・設計編」の3部で構成されています。第1章から第3章までが「入門編」です。第1章でAWSとTerraformのセットアップを行います。そして第2章と第3章で、Terraformの基礎知識を一気に学びます。第4章から第16章までが「実践編」です。第4章でシステムの全体設計を行います。

Terraformはインフラ構築をしていくために利用できるツールで、コードを記述することでAWSやGCPのようなクラウド上で構築するリソースを定義することができるため、Infrastructure as Codeを実現することができる。

The key features of Terraform are:
» Infrastructure as Code
Infrastructure is described using a high-level configuration syntax. This allows a blueprint of your datacenter to be versioned and treated as you would any other code. Additionally, infrastructure can be shared and re-used.
» Execution Plans
Terraform has a "planning" step where it generates an execution plan. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure.
» Resource graph
Terraform builds a graph of all your resources, and parallelizes the creation and modification of any non-dependent resources. Because of this, Terraform builds infrastructure as efficiently as possible, and operators get insight into dependencies in their infrastructure.
» Change Automation
Complex changesets can be applied to your infrastructure with minimal human interaction. With the previously mentioned execution plan and resource graph, you know exactly what Terraform will change and in what order, avoiding many possible human errors.
https://www.terraform.io/intro/index.html

本書を通じてTerraformの始め方、Terraformの基本的な操作や知識、そして実務における設計論や運用に関する知識を学ぶことができた。

本書では下記の技術スタックをTerraformで構築したため、コンテナを利用した一般的なアプリケーションにおけるアーキテクチャの構築ができたと考えて差し支えないだろう。

4.3テクノロジースタック
次のようなリソースを、各章で実装していきます(図4.1)。
・第5章「権限管理」:IAMポリシー、IAMロール
・第6章「ストレージ」:S3
・第7章「ネットワーク」:VPC、NATゲートウェイ、セキュリティグループ
・第8章「ロードバランサーとDNS」:ALB、Route53、ACM
・第9章「コンテナオーケストレーション」:ECSFargate
・第10章「バッチ」:ECSScheduledTasks
・第11章「鍵管理」:KMS
・第12章「設定管理」:SSMパラメータストア
・第13章「データストア」:RDS、ElastiCache
・第14章「デプロイメントパイプライン」:ECR、CodeBuild、CodePipeline
・第15章「SSHレスオペレーション」:EC2、SessionManager
・第16章「ロギング」:CloudWatchLogs、KinesisDataFirehose

そのためTerraformに限らず、AWSの全体的な技術スタックについても学ぶことができたと言える。

また本書ではAWSを使ったが、応用すればGCPやAzureについてもいろいろできると思われる。

通して写経した後での所感

AWSを知っていないと事あるごとに詰まる可能性あり

基本的にAWSについての概念が理解できていれば、詰まるところはほとんどない。

が、AWS自体を理解できていない場合は概念の理解に苦しむことになると思う。

本書ではAWSの各サービスについての簡単な説明はしてくれるものの、AWS自体を教えることを目的としているわけではないので、AWS自体が全く分からない場合は自力でAWSについて調べながら進めるか、別の本を先に読んでおく必要がある。

逆引き辞典としても使えそう

また、実務で一つ一つのリソースを構築するときの逆引きとしても使えそうだなという印象を持った。

ベストプラクティスも有用だし、ハンドブック的な役割の参考書としても持ってて損はない、と感じた

全体をこなしてみるまで実感は湧きづらい

本書を写経するだけだと一つ一つのリソースを作っている間はCLIでやるのとあまり変わらないのかも?という印象を受けてしまう。

ただそれら全てのリソースを一気に操作したいときにTerraformがかなり強力になってくるな、と感じた。

コスト削減のために作成したリソースを削除したいとなったときにGUIでポチポチしたり、CLIで一つ一つ削除するのは手間だし削除漏れが出る可能性もある。

Terraformであればterraform destroyだけでもれなく全てのリソースを削除することができるので、ミスがないし圧倒的に早く作業が完了する。

リソースを削除するときだけでなく、一度作ったインフラと同じ環境を再構築する場合も同様のメリットが考えられる。

本書で利用した・紹介されたコマンド一覧

備忘録として本書で利用・紹介されたコマンドの一覧と概要をまとめておく。

terraform init

作業ディレクトリをTerraformの実行のために初期化する。

設定を新規追加した場合やGitHubなどからクローンした場合に実行する。

The terraform init command is used to initialize a working directory containing Terraform configuration files. This is the first command that should be run after writing a new Terraform configuration or cloning an existing one from version control. It is safe to run this command multiple times.
https://www.terraform.io/docs/commands/init.html

terraform plan

「実行計画」が出力される、何が追加されたり削除されたりするのかが表示される。

これから実行される内容を把握するために用いる。

なお実際に実行が行われるわけではない。

The terraform plan command is used to create an execution plan. Terraform performs a refresh, unless explicitly disabled, and then determines what actions are necessary to achieve the desired state specified in the configuration files.
https://www.terraform.io/docs/commands/plan.html

terraform apply

planで出力される実行結果を実行する。
このコマンドで実際にリソースが追加・更新される。

The terraform apply command is used to apply the changes required to reach the desired state of the configuration, or the pre-determined set of actions generated by a terraform plan execution plan.
https://www.terraform.io/docs/commands/apply.html

terraform destroy

terraform applyで作成されたリソースを削除する。

ほとんどのリソースは問答無用に削除されるため、そうしたくない場合は適切な削除防止を行う必要がある。

The terraform destroy command is used to destroy the Terraform-managed infrastructure.
https://www.terraform.io/docs/commands/destroy.html

terraform get

.tfファイルの実行に必要なモジュールをダウンロードする。

The terraform get command is used to download and update modules mentioned in the root module.
https://www.terraform.io/docs/commands/get.html

terraform fmt

.tfファイルをフォーマットしてくれる。

-recursiveで再起的にフォーマットしてくれる。

The terraform fmt command is used to rewrite Terraform configuration files to a canonical format and style. This command applies a subset of the Terraform language style conventions, along with other minor adjustments for readability.
https://www.terraform.io/docs/commands/fmt.html

terraform validate

terraform fmtはフォーマットに関するコマンドだったが、こちらはファイルの記述に間違いがないかをチェックしてくれる。

静的解析ツール。

terraform -install-autocomplete

TerraformをCLIで操作するときのタブ保管機能をインストールしてくれる。

いらなくなったらterraform -uninstall-autocompleteで削除。

If you use either bash or zsh as your command shell, Terraform can provide tab-completion support for all command names and (at this time) some command arguments.
https://www.terraform.io/docs/commands/index.html#shell-tab-completion

tflint

terraform validateでは発見できないプロバイダーに起因する不具合を見つけてくれる。

tflint --deep --aws-region=ap-northeast-1 main.tfの様な記述をすればregion specificに実行も可能。

Since t1.2xlarge is a nonexistent instance type, an error will occur when you run terraform apply. But terraform plan and terraform validate cannot find this possible error beforehand. That's because it's an AWS provider-specific issue and it's valid as a Terraform configuration.
https://github.com/terraform-linters/tflint#why-tflint-is-required

terraform console

対話型のコンソールを開く。

terraform state

作成されたリソースに関する種々の情報を保持しているStateに関するコマンド群。

The terraform state command is used for advanced state management. As your Terraform usage becomes more advanced, there are some cases where you may need to modify the Terraform state. Rather than modify the state directly, the terraform state commands can be used in many cases instead.
https://www.terraform.io/docs/commands/state/index.html

terraform state list

作成されたリソースを列挙する。
追加の引数を与えればリソースの絞り込みが可能。

The terraform state list command is used to list resources within a Terraform state.
https://www.terraform.io/docs/commands/state/list.html

terraform state show

リソースの細かな情報を表示してくれる。

The terraform state show command is used to show the attributes of a single resource in the Terraform state.
https://www.terraform.io/docs/commands/state/show.html

terraform state pull

前提としてStateは基本的にはローカルで保持されるが、チーム開発の際には全体で同一のStateを持っておく必要があるためTerraform CloudやS3といったクラウドストレージなどで管理される。

このコマンドはそのステートをローカルに持ってくるためのコマンド。

The terraform state pull command is used to manually download and output the state from remote state. This command also works with local state.
https://www.terraform.io/docs/commands/state/pull.html

terraform state push

ローカルにあるStateでリモートのステートを書き換える。
基本的には使うべきではない。

The terraform state push command is used to manually upload a local state file to remote state. This command also works with local state.
https://www.terraform.io/docs/commands/state/push.html

terraform state rm aws_instance.remove

Terraformで管理しているリソースをStateから削除する。
あまり想像はつかないが、Terraformの管理から外したい場合に使う。

The terraform state rm command is used to remove items from the Terraform state. This command can remove single resources, single instances of a resource, entire modules, and more.
https://www.terraform.io/docs/commands/state/rm.html

terraform state mv

複数のステートがある場合に、あるステートから別のステートへリソースを移動させたいという場合に用いる。

The terraform state mv command is used to move items in a Terraform state. This command can move single resources, single instances of a resource, entire modules, and more. This command can also move items to a completely different state file, enabling efficient refactoring.
https://www.terraform.io/docs/commands/state/mv.html

terraform import

存在はしているがTerraformで管理されていないリソースをTerraformの管理下に持ってくるためのコマンド。

今まではGUIとCLIだけでAWSをやってたけど、これからTerraformを導入しよう!というときに使ったりしそう。

The terraform import command is used to import existing resources into Terraform.
https://www.terraform.io/docs/commands/import.html

terraform workspace

単一のコードで複数の環境を構築するWorkspaceについてのコマンド群。

The terraform workspace command is used to manage workspaces.
https://www.terraform.io/docs/commands/workspace/index.html

terraform workspace new

新規ワークスペースを追加する。

なおワークスペースについて操作を行なっていない場合、暗黙的にdefaultというワークスペースで作業をしていることになっている。

The terraform workspace new command is used to create a new workspace.
https://www.terraform.io/docs/commands/workspace/new.html

terraform workspace show

現在のワークスペースを表示する。

The terraform workspace show command is used to output the current workspace.
https://www.terraform.io/docs/commands/workspace/show.html

terraform workspace list

ワークスペースの一覧を表示する

The terraform workspace list command is used to list all existing workspaces.
https://www.terraform.io/docs/commands/workspace/list.html

terraform workspace select

作業を行うワークスペースを設定する。

The terraform workspace select command is used to choose a different workspace to use for further operations.
https://www.terraform.io/docs/commands/workspace/select.html

TF_LOG=debug

環境変数。

デバッグレベルのログを標準出力に出力することができる。
文字通りデバッグに使える。

Terraform has detailed logs which can be enabled by setting the TF_LOG environment variable to any value. This will cause detailed logs to appear on stderr.
https://www.terraform.io/docs/internals/debugging.html

terraform 0.12upgrade

0.12へのアップグレードのためのコマンド。
0.11以前のバージョンを使っている場合に、手作業ではなく公式のコマンドでアップグレードを行うことができる。

The terraform 0.12upgrade command applies several automatic upgrade rules to help prepare a module that was written for Terraform v0.11 to be used with Terraform v0.12.
https://www.terraform.io/docs/commands/0.12upgrade.html

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

AWS〜これから使う人向け〜

対象者と寄稿の背景

download.png

本記事の対象者は、AWSが意味不 or 調査しても不安がある(2種類のアカウントやよく出てくるワードが意味不)方向けです。
AWSの利用前に必要最低限の知識を整理整頓できる記事です。
「理解が貧しくわからないことがわからないので、調べたり、質問できない」とならないように
手助けできれば幸いです。

では、以下より本題に入ります。

AWSとは・・・??

簡潔にいうと、
インターネットサービスの総合スーパー(イオンモールみたいな)です:bangbang:

スーパー名:AWS
取扱商品:各サービス(EC2,S3,RDS などなど)
となります。(※イオンには、食品・文具・衣服・レストラン・書店など様々ですよね)

システム運用する際、これまでは・・・
①ドメインはドメイン屋、②ルーターはルーター屋、③データベースはデータベース屋さんでしたが、例えば上記①〜③のようなサービスをAWSが全て用意してます。
昔は、米は米屋、ペンや消しゴムは文房具屋、テレビは電気屋でしたが今はイオンに行けば全て揃います。
その役割をしているのがAWSです。

なので、約170のサービスがあり、利用者が自ら必要なサービスを選択して、組み合わせて利用します。
初心者の方が利用した際に、「意味不すぎて手が動かない」、「挫折するわ・・・」とならないように書いた記事なので、これを見て質問できるようになった、調べることが明確になったと、なることができれば嬉しいです:v:

下記より、戸惑うであろうワードや利用方法を噛み砕いて説明します。

登録すべきアカウントは2種類あり!?

Amazonで買い物などに使う、アカウントとAWSで使う下の2つは別物です:bangbang:
- rootユーザー
- IAMユーザー
-
rootユーザーとは・・・
支払いや契約状況など管理業務時に使うのが、rootユーザーアカウントです:bangbang:
AWS全体の管理者アカウントです、いわゆる管理人さんです。
このアカウントでサービス利用すると情報流出(個人情報やクレジットカード情報なども)の恐れがあります:sob:
rootユーザーがIAMユーザーで、操作できることに権限をつけたりもできます。

IAMユーザーとは・・・
AWSの各種サービスを使う際は、IAMユーザーアカウントです:bangbang:
これを使いたい!と思った時にこのアカウントでログインしサービスを利用します。
IAMはrootの管理下にあります。個人情報を持って無いアカウントなので、流出の危険もありません。

マネジメントコンソール

AWSのメニュー表です:bangbang:
画像はページの上部だけなので、実際はまだ下にもあります。
このメニューページをマネジメントコンソールと呼びます。
2020-03-11 9.41のイメージ.jpeg

実際どんなことができるのかといいますと、下記が一部です。
- サービスの選択、利用
- 請求確認
- スマホやタブレットからの管理
- AWSアカウント管理

ダッシュボード

操作画面です:bangbang:
マネジメントコンソールで商品を選択し、ダッシュボードが開きます。
その商品の稼働状況や、細かなサービスが表示されます。
下記、写真はEC2と言う商品を選んだ際のダッシュボードです。
リソース内では、現在の稼働状況が確認できます。
スクリーンショット 2020-03-11 9.58.55.png

リージョン

データセンターの場所です:bangbang:
スクリーンショット 2020-03-12 9.11.02.png
AWSのサーバーある地域です、世界に20箇所以上あります。日本は東京と大阪にありますが、個人利用は東京で、大阪は少し特殊なので、個人利用できません。
AWS利用が上達してくると、東京意外のリージョンを使うこと検討しないとな、と考える瞬間があります。

主要サービス名

下記にて、代表サービスをあげます。専門知識は必要なくて、マネジメントコンソールとダッシュボードから、簡単に使うことができます。自分で考えているものと一致したら、数回ボタンを押せば利用開始できます。
ここが、クラウドサービスのまさに強みです。
使いたいものを見つけたら、あとはボタン操作のみです。
1から構築となると数ヶ月かかります。コード書いて組み込んで、を繰り返すので時間かかりますが、AWSを使えば10分で完了します:v:
また、マネジメントコンソールで見えるワードのみ解説をしているので、ダッシュボードまでいくとさらに、
インスタンス?ElasticIP?などわからないことが発見できると思います。

では簡単ではありますが、紹介します。

EC2

サーバーになります:bangbang:
クラウドなので、なんといってもバックアップが取りやすいです。
また、EC2の設定変更も後々しやすいです。

S3

容量制限の無い、変化するストレージ:bangbang:
例えば、ライブハウスの使用量が・・・
2月は1000人、他の月は50人だとします。S3は、1000人サイズ、50人サイズと会場サイズを自動で変化してくれます。
なので、必要以上のお金を払わず、1000人超えても、また変化するのでパンクもしないです。
クラウドサービスでは無い場合、1年間1000人分を借りないといけません、なので必要無いお金も払う必要がありました。
パンクの恐れもあります。
この差が大きな特徴です。

RDS

データベースです:bangbang:
クラウド上でデータベースを使えるサービスです。利用できるサービスも1つではなく、6つあるので非常に柔軟に選択できます。ここでもクラウドで行う強みが発揮すます。
バックアップやアップデートを自動で行ってくれるので、データベース利用に我々は集中することが可能です。
データベースを利用するについては、この記事では割愛します。

以上、主要サービスの紹介でした。

AWS利用に適した人

結論は、設計ができる人や今後できるようになりたい人です。
なぜならば、AWSはレンタルショップではなく、販売店だからです。
この日数使うなら、「月額いくらです!」「何本借りれます!」という提案をしてくれません。
利用する僕らが、運用には「データベースはこれがいいな」「セキリュティはこのスペックにしよう」と自分で判断して使っていきます。

ただ決して、薄情なサービスではありません。実際に、日本政府も今後AWSを利用します
日本政府、基盤システムでAWSを採用
※Data Center Cafe様より拝借

多彩なサービス、聞き慣れないワードが多く難しさを感じるかもしれませんが、慣れてしまえば、使い勝手が良くワクワクします。分析やブロックチェーンなど専門家が行っている分野を興味ある人も手を動かして使うことができます。
イオンも自ら足を運べばどこに何があるかわかるのと同じで、AWSも是非どんどん使ってください!!

今回の記事は、以上です。
冒頭でもお話した通り、利用前に読んでいただきたい記事です。
仲間やSNS上で話題に上がったり悩まれてる方がいたらぜひ、おすすめしていただけると幸いです。

また、指摘やアドバイスもいただけますと幸いです。

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

使うときだけ EC2 インスタンスを立ち上げたい

はじめに

本番環境ならいざしらず、開発用の環境であったり、踏み台サーバーについては必要な時だけ立ち上げることで費用を抑えたいというのは当然のことでしょう。AWS にはリザーブドインスタンスや節約プランを使うことで費用を抑える仕組みもありますが、一般的な使い方では3割程度の割引率にしかならないため、平日日中だけしか利用しない場合には、随時起動/停止を行う方が安上がりです。

このエントリでは、私が考えた「使うときだけ EC2 インスタンスを起動する方法」を説明します。

方針

ここ最近、Linux であろうが Windows であろうが SSH 経由で接続することができるようになってきました。1

そこで「SSH で接続すると起動して、SSH を切断すると停止する」ような仕組みを考えます。昨年から Session Manager を使った SSH トンネリングが利用できるようになったので、これを使い次のような仕掛けを行うことにしました。

  1. (クライアント環境)SSH の ProxyCommand で、トンネリングのついでにインスタンスの起動スクリプトを実行する
  2. (サーバー環境)EC2 インスタンスの中で Session Manager の接続状況を cron で監視して、接続がないときは停止する

以降では具体的なスクリプトを紹介します。なお、私は Windows ユーザーなので、クライアント環境のスクリプトはバッチファイルとなっています。クライアント環境として MacOS や Linux をお使いの方は、シェルスクリプトに読み替えて頂ければ幸いです。

事前準備

クライアント環境ではあらかじめ次のことを行っておきます。
- OpenSSH クライアントのインストール
- AWS CLI のインストールとプロファイル設定
- Session Manager Plugin のインストール

サーバー環境でもあらかじめ次のことを行っておきます。
- AWS CLI のリージョン設定
- SSM エージェントのインストール2
- EC2 インスタンスの IAM ロールに対する権限の付与

権限についてはSSM エージェント用の AmazonSSMManagedInstanceCore だけでなく、スクリプト用に次の権限が必要です。

  • ssm:DescribeSessions
  • ec2:DescribeInstanceTypes
  • 自身のインスタンスへの ec2:StopInstances

クライアント環境への設定

まず、クライアント環境の設定です。Session Manager を使って EC2 インスタンスに SSH で接続する場合には ProxyCommand を使います。例えば、%HOME%/.ssh/config に次のようなショートカットが設定してあるとします。

%HOME%/.ssh/config
Host aws_bastion
    HostName i-12345
    User ec2-user
    Port 22
    IdentityFile ~/.aws/ec2.pem
    ProxyCommand C:/Program Files/Amazon/AWSCLIV2/aws.exe ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p

この ProxyCommand を独自のスクリプトに置き換え、セッション開始の前に EC2 インスタンスを起動するようにします。ここでは、C:\ssm_connect.bat というスクリプトに記述することにし、.ssh/config を書き換えます。

%HOME%/.ssh/config
Host aws_bastion
    HostName i-12345
    User ec2-user
    Port 22
    IdentityFile ~/.aws/ec2.pem
    ProxyCommand C:\\Windows\\System32\\cmd.exe /c C:/ssm_connect.bat %h %p default

次に ProxyCommand から呼び出される ssm_connect.bat を用意します。長いですが、単に EC2 インスタンスが止まっていたら起動させて接続できるまで待つというだけのスクリプトです。

ssm_connect.bat
@echo off
setlocal

set AWS_HOME=C:\Progra~1\Amazon\AWSCLIV2
set INSTANCE_ID=%1
set PORT=%2
set PROFILE=%3

if "%PROFILE%"=="" (
    set PROFILE=default
)

set INSTANCE_STATE=
for /f "usebackq" %%R in (`%AWS_HOME%\aws.exe ec2 describe-instance-status --include-all-instances --instance-ids "%INSTANCE_ID%" --query "InstanceStatuses[*].InstanceState.Name" --output text`) do set INSTANCE_STATE=%%R
if "%INSTANCE_STATE%"=="pending" (
    rem no handle
) else if "%INSTANCE_STATE%"=="running" (
    rem no handle
) else if "%INSTANCE_STATE%"=="rebooting" (
    rem no handle
) else if "%INSTANCE_STATE%"=="stopping" (
    echo Waiting the instance stopped... 1>&2
    aws ec2 wait instance-stopped --instance-ids "%INSTANCE_ID%" --profile "%PROFILE%" > nul
    if not errorlevel 0 (
        echo Failed to invoke ec2:wait instance-stopped: %INSTANCE_ID% 1>&2
        exit /b 1
    )
    aws ec2 start-instances --instance-ids "%INSTANCE_ID%" --profile "%PROFILE%" > nul
    if not errorlevel 0 (
        echo Failed to invoke ec2:start-instances: %INSTANCE_ID% 1>&2
        exit /b 1
    )
) else if "%INSTANCE_STATE%"=="stopped" (
    aws ec2 start-instances --instance-ids "%INSTANCE_ID%" --profile "%PROFILE%" > nul
    if not errorlevel 0 (
        echo Failed to invoke ec2:start-instances: %INSTANCE_ID% 1>&2
        exit /b 1
    )
) else if "%INSTANCE_STATE%"=="shutting-down" (
    echo Instance is shutting-down: %INSTANCE_ID% 1>&2
    exit /b 1
) else if "%INSTANCE_STATE%"=="terminated" (
    echo Instance is terminated: %INSTANCE_ID% 1>&2
    exit /b 1
) else (
    echo Failed to invoke ec2:describe-instance-status: %INSTANCE_ID% 1>&2
    exit /b 1
)

echo Waiting the instance status ok... 1>&2
aws ec2 wait instance-status-ok --include-all-instances --instance-ids "%INSTANCE_ID%" --profile "%PROFILE%" > nul
if not errorlevel 0 (
    echo Failed to invoke ec2:wait instance-status-ok: %INSTANCE_ID% 1>&2
    exit /b 1
)

%AWS_HOME%\aws.exe ssm start-session --target "%INSTANCE_ID%" --document-name AWS-StartSSHSession --parameters "portNumber=%PORT%

ここまで来れば、あとは「ssh aws_bastion」と打ち込んで、接続するだけです。

停止中の場合、 EC2 インスタンスの起動には1~2分程度かかりますが気長に待ちましょう。Hibernation が可能なインスタンスであれば、もう少し早く起動できるかもしれません。(未確認です)

VSCode の Remote SSH から使う場合には接続タイムアウト時間を 3 分程度に延長するとよいでしょう。

サーバー環境への設定

定期的に Session Manager のセッション数を確認し、セッションがない場合には自分のインスタンスを停止するスクリプトを作ります。ここでは ~/bin/stop_instance.sh にスクリプトを配置することにします。

~/bin/stop_instance.sh
#/bin/bash -e

UPTIME=`uptime -s`
B5TIME=`date +"%Y-%m-%d %T" --date "-5 minutes"`
if [[ $UPTIME > $B5TIME ]]; then
    echo "Skip stopping instance for initializing."
    exit 0
fi

TOKEN=`curl -s -X PUT http://169.254.169.254/latest/api/token \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`

INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id \
    -H "X-aws-ec2-metadata-token: ${TOKEN}"`

INSTANCE_TYPE=`curl -s http://169.254.169.254/latest/meta-data/instance-type \
    -H "X-aws-ec2-metadata-token: ${TOKEN}"`

HIBERNATE_SUPPORTED=`aws ec2 describe-instance-types \
    --instance-types $INSTANCE_TYPE \
    --query "InstanceTypes[*].HibernationSupported" \
    --output text`
if [ "$HIBERNATE_SUPPORTED" != "true" ]; then
    HIBERNATE_SUPPORTED=false
fi

OPTIONS=
if [ "$HIBERNATE_SUPPORTED" = "true" ]; then
    OPTIONS="${OPTIONS} -hibernate"
fi

SESSION_COUNT=`aws ssm describe-sessions --state Active \
    --filters "key=Target,value=${INSTANCE_ID}" \
    --query "Sessions[*].[SessionId]" \
    --output text | wc -l`
if [ $SESSION_COUNT -gt 0 ]; then
    echo "Skip stopping instance for sessions existed: ${INSTANCE_ID}"
    exit 0
fi

aws ec2 stop-instances --instance-ids $INSTANCE_ID \
    $OPTIONS > /dev/null
if [ $? -ne 0 ]; then
    echo "Fails to stop instance: ${INSTANCE_ID}" >&2
    exit 1
fi

echo "Stopping instance: ${INSTANCE_ID}"

exit 0

あとは、このスクリプトを cron から呼び出します。5分ごとに確認する場合、 crontab -e で次の行を追加すれば完了です。

*/5 * * * * ~/bin/stop_instance.sh | logger -t stop_instance

これで接続がないときには自動的に停止するようになります。

注意点

VisualStudio Code の Remote SSH で今回紹介したスクリプトを組み合わせた場合、VSCode 側で接続を切断しても、なぜか Session が残り続けるという問題が起こっています。この場合は、AWS マネージメントコンソールから Session Manager の画面を開き、セッションを切断する必要があります。


  1. Windows 2019 からは OpenSSH が OS 機能として用意されています。SSH サーバーをインストールしておけば、リモートデスクトップ接続も SSH のポートフォワーディング経由で利用することができます。 

  2. SSMAgent を利用するには、AWS のサービス API にアクセスが必要なため、インターネットに接続可能か、該当サービスへのプライベートリンクが設定されている必要があります。 

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

2週間でAWS認定ソリューションアーキテクト-アソシエイトに合格したので、合格までの目安をまとめてみます

はじめに

こんにちは、Tamariと申します。
この度、AWS認定ソリューションアーキテクト-アソシエイト試験に合格したので、(1回落ちて、2回目での合格)合格までの目安などをまとめられたらと思います。

前提

・未経験

私自身、社会人15年目ぐらいになりますが、新卒の会社と、直近の2年間くらいしかIT業界におらず、AWSというサービスの概要は知っていましたが、実際には操作したこともなく、細かいEC2、S3等のサービス名は知らず、未経験となります。

やったこと

全体像をつかむ

未経験でサービス名とか全くわからない状態だと、問題読んでもさっぱり何を言っているのかわからないので、まずはUdemyと黒本を用いてサービス全体を掴むような学習をしました。

Udemy

Udemyでは以下のコンテンツで学習をしました。22時間とありますが、動画の時間が22時間なので、ハンズオンを合わせると、動画を1.5倍速でやったとしても、30時間以上はかかるかと思います。
・これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(初心者向け22時間完全コース)

黒本

黒本も全体的なサービスの説明や、試験で問われる可用性などの観点事に章がまとまっているので、読んで参考にしました。上記のUdemyコンテンツをやり終えた後だと、比較的内容がスッと入ってきました。また、模擬テストもついてるので繰り返し解きました。
徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書

問題演習

問題演習を行ったのは、以下の4種類です。
主観ですが、難易度を1〜3に分類してみました。
No.1〜3を2回ずつくらい解いた時点で、1回目の模擬試験を受けたのですが、700点で不合格でした。。(おそらく後1問正解していれば合格。。)

No 模擬試験名 難易度 テストの数 
1 黒本 1.5 1
2 Amazon公式 1 1
3 これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(初心者向け22時間完全コース(Udemy) 2 3
4 AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(5回分325問) (Udemy) 3 4

1回目の本試験受験時の習熟度と結果

No 模擬試験名 1回目正答率 2回目正答率 
1 黒本 49% 83%
2 Amazon公式 72% -
3 これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(初心者向け22時間完全コース(Udemy)模擬試験① 35% 64%
4 これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(初心者向け22時間完全コース(Udemy)模擬試験② 41% 75%
5 これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(初心者向け22時間完全コース(Udemy)模擬試験③ 52% 75%

スクリーンショット 2020-02-23 8.52.52.png

模擬試験は受験後、解説を理解し、繰り返し受験することで、点だった知識が徐々に線になっていく感覚がありました。学習を始めてから1週間後に試験を受けましたが、700点で不合格でした。。
5種の模擬試験で、7割程度正解できるようになっていましたが、試験問題は多種多様で、模擬試験よりも難しいなと感じた問題もありました。

2回目の本試験受験時の習熟度と結果

後ほど、まとめられたらと思います。
試験については、無事合格していました。

スクリーンショット 2020-03-14 14.54.27.png

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