20210314のAWSに関する記事は27件です。

【学習メモ】AWS Lambda

Lambda

サーバーレス。
FaaS(Functions as a Service)
AWS Lambdaはサーバーがなくても、コードを実行することで効率的なアーキテクチャを実現できるサービス。

特徴

・実行基盤は全てAWSが管理
・AWSサービスと連携させることで簡単にイベントドリブンなアプリケーションを実装可能
・100ミリ秒単位でコード実行時間に対しての課金でありコスト効率が非常に高い
・オートスケール
・Python/Java/Node.js/C#/Go/Rubyで書かれたコードを実行

仕組み

イベントの発生がトリガーとして、Lambdaコートが実行される。

利用モデル

Pushモデル

・AWSサービスとカスタムイベントが直接実行することによって、Lambdaへイベント通知して、コードを実行する。
※コード実行の順序は保証されない

Pullモデル

・LambdaはDynamoDBとKinesisなどのデータ処理へポーリングを行い、コードを実行する。
※一度ストリームに入れることによって、イベントの順序を保つことができる

パーミッション

Pullモデルの場合、ストリーム側 (invocation role) と、実行側 (excution role) の両方のパーミッションが必要。

◯Execution:
 ・LambdaファンクションがAWSリソースにどういったアクションを実施させるかを決定する。
 ・指定されたIAMロールに沿ってAWSのリソースへのアクセスが許可される。
◯Invocation:
 ・Lambdaファンクションをどのリソースが実行できるかを決定する。

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

django+docker+AWS EC2+AWS S3でポートフォリオサイトをデプロイ

◆AWS S3との連携

以下のサイトを参考にしました。

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

【初心者】Amazon VPC フローログを使ってみる

1. 目的

2. やったこと

  • 検証用のVPCとインスタンスを作成する。
  • VPC全体に対して、VPCフローログの取得設定を行う。
  • 試験トラフィックを発生させ、VPCフローログにどのように記録されるか確認する。

3. 構成図

構成図.png

4. 設定手順

4.1 事前準備

  • 検証用のVPCなどを作成する。(上記構成図の通り)
    • VPC、パブリックサブネット、パブリックサブネット上のインスタンス(EIP付与)
    • インスタンスタイプはt3.micro (Nitro世代インスタンスの場合のみ詳細に出力できるフィールドがあり、それを確認したいため)

4.2 フローログの設定

  • VPC全体のフローログを取得する設定を行う。
  • VPCを選択 -> アクション -> 「フローログを作成」
  • 今回はAthenaでログを検索したいため、「送信先」はS3を選択する。
  • ログレコード形式で「AWSのデフォルト形式」を選択すると、2021/3/3に追加されたフィールドが含まれない。そのため、「カスタム形式」を選択し、「AWSのデフォルト形式」に含まれるフィールドを全て手動で選択した上で、新しいフィールド(赤枠の4つ)を追加する。

flow01a.png

flow02a.png

4.3 Athena検索の設定

  • S3に保存されるVPCフローログを、Athenaで検索できるようにする。
  • 公式ドキュメント「Amazon VPC フローログのクエリ」に従い設定する。
  • 基本的に手順通り行えばよいが、テーブル作成時に新しく追加したフィールド(4つ)を含める。
# テーブルの作成
CREATE EXTERNAL TABLE IF NOT EXISTS vpc_flow_logs (
  version int,
  account string,
  interfaceid string,
  sourceaddress string,
  destinationaddress string,
  sourceport int,
  destinationport int,
  protocol int,
  numpackets int,
  numbytes bigint,
  starttime int,
  endtime int,
  action string,
  logstatus string,
  pkt_src_aws_service string,
  pkt_dst_aws_service string,
  flow_direction string,
  traffic_path int
)
PARTITIONED BY (`date` date)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ' '
LOCATION 's3://[BUCKETNAME]/AWSLogs/[ACCOUNTID]/vpcflowlogs/ap-northeast-1/'
TBLPROPERTIES ("skip.header.line.count"="1");

# パーティションの作成
ALTER TABLE vpc_flow_logs
ADD PARTITION (`date`='2021-03-10')
LOCATION 's3://[BUCKETNAME]/AWSLogs/[ACCOUNTID]/vpcflowlogs/ap-northeast-1/2021/03/10';
  • フィールド名にハイフンがある(例えばtraffic-pathなど)と以下のエラーになるため、「traffic_path」などのように修正した。
# エラーメッセージ(ハイフンがある場合)
line 1:8: no viable alternative at input 'create external' (service: amazonathena; status code: 400; error code: invalidrequestexception; request id: 757d9049-a6a7-4753-b554-38df81adbecb; proxy: null)

5. 試験トラフィックの生成及びフローログの確認

  • トラフィックを発生させ、それらのトラフィックがどのように記録されるかを確認する。

5.1 外部(インターネット)からのsshアクセス

  • 手元の作業用PCから、VPCフローログが有効化されているVPCのインスタンスへsshアクセスする。
  • インスタンス(10.0.0.188)へのssh(22/tcp)のacceptが記録されている。

flow04a.png

5.2 インスタンスからのhttps/pingアクセス

  • 検証用のインスタンスから、インターネット(例:www.yahoo.co.jp)へのhttps(curl)/pingのアクセスを行う。
  • 2行目にhttpsアクセス(443/tcp)、3行目にpingアクセス(protocol 1 = icmp)が記録されている。
  • その他、1行目は作業用PCからのsshアクセスへの応答、4~6行目は外部へのNTPアクセスが記録されている。

flow05a.png

5.3 インスタンスからのS3アクセス

  • 検証用のインスタンスから、VPCエンドポイントがない状態で、インターネットGW経由でS3アクセスを行う。その後、S3 VPCエンドポイント(Gatewayタイプ)を作成し、エンドポイント経由でS3アクセスを行う。通信経路の違いでログがどのように変わるかを確認する。
  • 一番下の行(startime 1615437144 = 2021-03-11 13:32:24(JST)) は、VPCエンドポイントがない状態で、インスタンスで"aws s3 ls"コマンドを実行した際のログ。「traffic-path」の値が「8」(Internet Gateway経由) になっている。
  • 上から3行目(starttime 1615445753 = 2021-03-11 15:55:53(JST))は、S3 VPCエンドポイントを作成した後、インスタンスで"aws s3 ls"コマンドを実行した際のログ。「traffic-path」の値が「7」(VPC Endpoint経由) になっており、経路が変わったことが確認できる。
  • 上記の7と8が区別できるのは2021/3時点ではNitroインスタンスのENIのみ。

flow06a.png

6. 所感

  • 今回、やっと使い方が分かったので、トラフィックが想定通りに通らない時とかの調査に活用するようにしたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS_Cloudformationで環境構築してみた②

前回の記事
https://qiita.com/shinichi_yoshioka/items/487662c0749fe7b1ca8c

前回の続きで、Networkレイヤは作成済。
以下のシンプルな構成で、今回はEC2インスタンスとセキュリティグループをCloudFormationで作成します。
CloudFormation構成図.png

テンプレートの作成

①CloudFormationのymlを作成するにあたって、CloudFormationのRain(CLI実行ツール)を使ってテンプレート(EC2用ymlとセキュリティグループ用yml)を出力する。
Rainの使い方の詳細は以下のURLを参照。
https://dev.classmethod.jp/articles/aws-cloudformation-rain/

②出力されたymlそれぞれをクロススタック参照するようにパラメータを入れる。
Application.yml (EC2インスタンス作成)

AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: Secret-key
      AvailabilityZone: ap-northeast-1a
      ImageId: ami-0f27d081df46f326c
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          #DeleteOnTermination: false # Optional
          #Description: CHANGEME # Optional
          DeviceIndex: "0"
          GroupSet:
            - !ImportValue TEST-SGfromCF
          SubnetId: subnet-068305cddc05d01bd
      #SecurityGroupIds:
        #- !ImportValue TEST-SGfromCF
      #SecurityGroups:
        #- !ImportValue TEST-SGfromCF
      #SubnetId: subnet-068305cddc05d01bd
      Tags:
        - Key: Name
          Value: TEST-EC2_fromCF
      #UserData: CHANGEME # Optional
      #Volumes:
        #- Device: CHANGEME
          #VolumeId: CHANGEME

パラメータメモ
KeyName:秘密鍵の名前(.pemは不要)
ImageId: ami-0f27d081df46f326c ←AmazonLinux
AssociatePublicIpAddress: true ←パブリックIPアドレスを自動で割り当てる
DeviceIndex: "0" ←自動でパブリックIPアドレスを割り当てる際は"0"にする
GroupSet: Import関数でセキュリティグループを参照

Secutiry.yml

AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH from ALL
      SecurityGroupEgress:
        - Description: Outbound
          CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          #CidrIpv6: CHANGEME # Optional
          #DestinationPrefixListId: CHANGEME # Optional
          #DestinationSecurityGroupId: CHANGEME # Optional

      SecurityGroupIngress:
        - Description: Inbound
          CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          #CidrIpv6: CHANGEME # Optional
          #SourcePrefixListId: CHANGEME # Optional
          #SourceSecurityGroupId: CHANGEME # Optional
          #SourceSecurityGroupName: CHANGEME # Optional
          #SourceSecurityGroupOwnerId: CHANGEME # Optional

      Tags:
        - Key: Name
          Value: TEST-SG
      VpcId: vpc-0af10c6c61fb430cc

Outputs:
  MySecurityGroupGroupId:
    Value: !GetAtt MySecurityGroup.GroupId
    Export:
      Name: TEST-SGfromCF

パラメータメモ
SecurityGroupEgress: セキュリティグループのアウトバウンド
SecurityGroupIngress: セキュリティグループのインバウンド
Outputs: GetAtt関数を用いてMySecurityGroupのGroupIdを「TEST-SGfromCF」の名前タグをつけてエクスポートする

スタックを作成

マネージメントコンソール- [CloudFormation]から[スタックの作成]をクリックし、
Application.ymlとSecutiry.ymlをアップロードして、スタックを作成する。
Security-Layer.PNG
Application-Layer.PNG
イベントタブのステータスがCREATE_COMPLETEになっていれば、正常に作成されたことを確認できる。
作成したEC2にセキュリティグループが適用され、EC2に秘密鍵を使ってSSH接続できることを確認した。

EC2.PNG

最後に

EC2のセキュリティグループに関して紛わしい部分があるので、以下にメモを残しておきます。
①NetworkInterfaces配下のGroupSet・・・EC2インスタンス起動時に新しくネットワークインターフェースを作成する場合にセキュリティグループのIDを参照する際に使用
②SecurityGroups・・・デフォルトVPCのセキュリティグループを作成する際に使用
③SecurityGroupIds・・・推奨。参照するセキュリティグループ(AWS::EC2::SecurityGroup)を作成して、それを参照する際に使用

謎.PNG

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

AWS DynamoDB テーブル一覧取得 メモ

テーブル一覧取得

resource

import boto3

dynamodb = boto3.resource('dynamodb')
response = dynamodb.tables.all()
print(type(response))
print(response)
print('------------')
for x in response:
    print(x, x._name, type(x))
<class 'boto3.resources.collection.dynamodb.tablesCollection'>
dynamodb.tablesCollection(dynamodb.ServiceResource(), dynamodb.Table)
------------
dynamodb.Table(name='TestTable1') TestTable1 <class 'boto3.resources.factory.dynamodb.Table'>
dynamodb.Table(name='TestTable2') TestTable2 <class 'boto3.resources.factory.dynamodb.Table'>

client

import boto3

dynamodb = boto3.client('dynamodb')
response = dynamodb.list_tables()
print(type(response))
print(response['TableNames'])
<class 'dict'>
['TestTable1', 'TestTable2']

参考記事

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

AWSでIPフローティングを試してみた!

AWSでIPフローティングを試してみた!

以前、IPフローティングについて調べてみたという記事を書いたときに、機会があったら試してみようと思っていたので、今回実際にやってみようと思います!
簡単なおさらいですが、IPフローティングというのはEC2インスタンスにアタッチしているEIPを別のEC2インスタンスに付け替えることです。
早速やっていきましょう!

※今回はEC2インスタンスの構築手順から紹介していますが、「そんなの知ってるよ!」という方はEIPを手動で付け替えてみるまでスキップして頂いても構いません。

構成図

今回はシンプルに以下のような構成で試してみようと思います。
EIPの付け替えはAPIを使えば自動化できますが、今回は手っ取り早くやりたいので手動で付け替えます。

image.png
CDP:Floating IPパターン - AWS-CloudDesignPatternより

前提

Route 53にドメインを登録している前提で進めますので、ドメインがない方はRoute 53で登録するか(有料)、サードパーティの無料ドメインや低価格ドメインを取得してRoute 53に登録してください。
登録方法についてはググるといっぱい出てきますので、ネットの記事を参考にしてください。
検索例:Route53 ドメイン お名前.com

EC2インスタンスを構築する

まずは適当なEC2インスタンスをWebサーバーとして起動します。

マネジメントコンソールからEC2コンソールに移動します。
image.png

インスタンスを起動をクリックします。
image.png

今回は無料枠のLinux 2を使用します。
image.png

インスタンスタイプも無料枠のt2.microにします。
image.png

インスタンスの詳細は以下のように設定しました。
購入のオプション:スポットインスタンスのリクエスト
VPC:作成済みのVPC
サブネット:作成済みのパブリックサブネット
キャパシティーの予約:なし
image.png

ユーザーデータでは以下のようにApacheをインストールしてWebサーバーにするよう設定しています。

#!/bin/bash
yum update -y
yum -y install httpd
chkconfig httpd on
service httpd start

その他のIAMロールやモニタリングの設定はデフォルトのままです。
image.png

ストレージの設定はデフォルトのままとします。
image.png

タグはPrimaryとしました。
image.png

セキュリティグループは作成済みのグループを使用します。
とりあえずHTTPとSSHのマイIPを許可していればいいと思います。
image.png

以上の設定でEC2インスタンスを起動します。
image.png

キーペアは作ってなかったので新しく作りました。
image.png

作成リクエスト送信!
image.png

スポットインスタンスで起動したのでこの画面です。
image.png

インスタンス一覧からパブリックIPをコピペしてアクセスしてみます。
image.png

無事Apacheのデフォルトページが表示されました。
接続がうまくいかない場合はセキュリティグループでHTTPを許可しているか、サブネットがパブリックか、EC2インスタンスにパブリックIPが付与されているかなどをご確認ください。
image.png

中身をちょっと編集

後ほど作成するセカンダリーのEC2インスタンスと区別がつくように、表示を変えておこうと思います。
僕はTera Termでインスタンスに入りますがお好きなソフトをお使いください。

ホストにEC2インスタンスのパブリックIPを入力します。
image.png

続行をクリックします。
image.png

ユーザー名はデフォルトではec2-userとなっているのでそれを入力し、ダウンロードしたキーペアを指定して接続します。
image.png

入れました。
接続がうまくいかない場合はセキュリティグループでSSH接続を許可しているかなどをご確認ください。
image.png

以下のコマンドで管理者権限でindex.htmlを作成していきます。

sudo su
cd /var/www/html
nano index.html

image.png

適当に<h1>This is Primary</h1>とか入力しておきます。
あとはCtrl + X → Y →Enterで保存すると元の画面に戻ります。
image.png

一応ファイルができてるかlsコマンドで確認しておきます。
うん、ちゃんとできてる。
image.png

表示も確認しておきましょう。
できてますね!
image.png

セカンダリーのEC2インスタンスを構築

同じ手順でセカンダリーのEC2インスタンスも構築しますが、同じ手順なので変更点だけ書いておきます。
・サブネット:セカンダリーはap-northeast-1cにしました。
・タグ:Secondary
・htmlファイル:<h1>This is Secondary</h1>
image.png

プライマリーにEIPをアタッチ

プライマリーのEC2インスタンスに固定IPであるEIPをアタッチしていきます。

EC2ダッシュボードからElastic IPをクリックします。
image.png

Elastic IP アドレスの割り当てをクリックします。
image.png

タグにFloating-Testと付けて、あとはデフォルトのまま割り当てをクリックします。
image.png

EC2インスタンスへの関連付けを行います。
image.png

プライマリー用のEC2インスタンスとプライベートIPアドレス(自動入力)を設定し、再関連付けにチェックを付けておきます。
image.png

関連付けされたら試しにEIPでのアクセスもしておきます。
プライマリー側が表示されればOKです。
image.png

Route 53でドメインとEIPの紐づけ

続いてRoute 53での設定を行います。
ひとまずRoute 53のコンソールに移動しましょう!
image.png

作成済みのホストゾーンに移動します。
image.png

登録済みのドメインをクリックします。
image.png

レコードを作成します。
image.png

レコード名をにfloatingと入力し、値にEIPを入力します。
TTLは試しに0秒にしてみました。
image.png

floating.<ドメイン名>でアクセスしてみます。
プライマリー側の表示が確認できました!
image.png

EIPを手動で付け替えてみる

ようやくやりたいことにたどり着きました!
今回はプライマリーにアタッチしているEIPをセカンダリーに手動で切り替えてみたいと思います。

EIPのコンソールから関連付けを変更していきます。
image.png

セカンダリーのEC2インスタンスを選択します。
image.png

ドメイン名でアクセスしてみました。
2~3秒でプライマリーからセカンダリーに切り替わりました。はやっ!!
image.png

試しにTTLを変えてみる

先ほどはRoute 53でTTLを0秒に設定していましたが、今度は60秒に変更してみます。
EIPの付け替えだけならTTLの影響は受けないはずなので、切り替わりもすぐ行われるはずです。
image.png

セカンダリーのEIPをプライマリーに付け替えてっと。
さて、どうかな?
image.png

おお!さっきと一緒ですぐ切り替わった!
って、そりゃそうですよね。ドメイン名とIPの紐づけをキャッシュしている時間がTTLだと思っているので、関連付けられているIPが変わらない限りTTLは関係ないですね。今回はIPの先にあるインスタンスを変えているだけです。
image.png

Route 53のヘルスチェックを用いたフェイルオーバーもやったことありますが、あちらは切り替えに1分~2分ほどかかり、なおかつブラウザのキャッシュにも影響されていたので、切り替え時間だけならIPフローティングの方が速そうです。

まとめ

今回は、以前調べたIPフローティングを実際に試してみました。切り替え時間がすごく速くてびっくりしました!ただ今回は手動での付け替えだったので、実務で使う場合はAPIを使って自動化するのが良いと思います。その際、EC2インスタンスの監視に何を使うのが良いのかは要件などにもよりそうですが、AWSのサービスや監視ソフトといったところでしょうか。
今度はそのあたりの組み合わせも試せたらと思っています。
それでは今回はこのへんで!

課金に関する注意点

構築したEC2インスタンスは不要な場合削除しましょう。また、EIPは関連付けを外して解放しましょう。EIPはアタッチされていない時間や停止したインスタンスに関連付けられていると課金されてしまいます。EC2インスタンスも停止だけだとEBSの料金がかかりますので、不要な場合は削除しておきましょう!

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

AWS Signature v4の署名をJavaで作る「完全な署名バージョン4」for Java

AWS Sig V4とは

AWSのAPIを利用するときに、クライアント側からI AMユーザであることを示す為に送付する署名のことです。前はバージョン2とかいろいろあったみたいですが、今はV4がメインで使われているようです。
SigV4はAWS公式のルールにしたがって作り、HTTPリクエストのAuthorizationヘッダーの値に載せて送ります。それを受け取ったAWS側では、同じロジックに従って署名を作り、送られてきた署名と一致するかを確かめることで認証を行います。

API GatewayのI AMユーザを使ったアクセス制限を例に、今回は自前で実装してみます。

SigV4の使い方

POSTであればリクエストヘッダーにAuthorization、X-Amz-Dateの二つを載せて送ります。GETであればクエリパラメータ文字列でもいけるようです。その2つの文字列を作るのにはルールがあり、特にAuthorizationのほうは面倒なルールに従って文字列を生成する必要があります。
本来であれば、SDKを使えば簡単にできるところですが、どんなことをしているのか中身が気になったので自前で書いてみました。

自前で署名するプログラム

https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html
公式ドキュメントを参考にして署名をします。
手順は4つです。

  1. タスク 1: 正規リクエストを作成する
  2. タスク 2: 署名文字列を作成する
  3. タスク 3: 署名を計算する
  4. タスク 4: HTTP リクエストに署名を追加する

どの文字列を作るのにも改行の場所とか、暗号化する対象とか、色々気を使いながらルールに従って署名をします。簡単そうに見えていましたが、1文字でも違うと認証はじかれるので、結構ハマりました。

実際に書いてみたプログラムは以下です。
以下の関数doShomei()はパラメータは全部べた書きなので適宜パラメータ化したりなんなりしたほうが良いですね。

package sample.XXXX;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;

public class Shomei {
    private static final Log LOG = LogFactory.getLog(Shomei.class);

    public void doShomei() {
        /*
         * タスク1. 署名バージョン4の正規リクエストを作成する.
         *  https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-canonical-request.html
         *  CanonicalRequest =
         *      HTTPRequestMethod + '\n' +
         *      CanonicalURI + '\n' +
         *      CanonicalQueryString + '\n' +
         *      CanonicalHeaders + '\n' +
         *      SignedHeaders + '\n' +
         *      HexEncode(Hash(RequestPayload))
         */
        SimpleDateFormat xAmzDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        xAmzDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        String xAmzDate = xAmzDateFormatter.format(new Date()).trim();

        String host = "XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com";
        String httpRequestMethod = "POST";
        String canonicalUri = "/hoge/fuga";
        String signedHeaders = "content-type;host;x-amz-date";
        String canonicalQueryString = "";
        String contentType = "application/json";
        String canonicalHeaders = "content-type:" + contentType + "\nhost:" + host
                + "\nx-amz-date:"
                + xAmzDate;
        String requestPayload = "";
        String canonicalRequest =
                httpRequestMethod + '\n' +
                        canonicalUri + '\n' +
                        canonicalQueryString + '\n' +
                        canonicalHeaders + '\n' + '\n' +
                        signedHeaders + '\n' +
                        DigestUtils.sha256Hex(requestPayload);

        LOG.debug(canonicalRequest);
        String hashedCanonicalRequest = DigestUtils.sha256Hex(canonicalRequest);

        /*
         * タスク2. 署名バージョン4の署名文字列を作成する.
         * https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-string-to-sign.html
         * StringToSign =
         *  Algorithm + \n +
         *  RequestDateTime + \n +
         *  CredentialScope + \n +
         *  HashedCanonicalRequest
         */
        String date = "20210314";
        String region = "ap-northeast-1";
        String service = "execute-api";
        String endStr = "aws4_request";
        String algorithm = "AWS4-HMAC-SHA256";
        String credentialScope = date + "/" + region + "/" + service + "/" + endStr;

        String stringToSign = algorithm + "\n"
                + xAmzDate + "\n"
                + credentialScope + "\n"
                + hashedCanonicalRequest;

        /*
         * タスク3. 署名バージョン4の署名を計算する.
         * https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-calculate-signature.html
         * 
         * kSecret = your secret access key
         * kDate = HMAC("AWS4" + kSecret, Date)
         * kRegion = HMAC(kDate, Region)
         * kService = HMAC(kRegion, Service)
         * kSigning = HMAC(kService, "aws4_request")
         */
        String accessKey = "API Gatewayに実行権限を持つIAMユーザーのアクセスキー";
        String secretKey = "API Gatewayに実行権限を持つIAMユーザーシークレットキー";
        try {
            /*
             * Javaを使用して署名キーを取得
             * https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
             */
            byte[] key = getSignatureKey(secretKey, date, region, service);
            String signature = String.valueOf(Hex.encodeHex(hmacSHA256(stringToSign, key)));
            LOG.debug(signature);

            /*
             * タスク4. HTTPリクエストに署名を追加する
             * https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-add-signature-to-request.html
             * 
             * Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
             */
            String authorization = algorithm + " Credential=" + accessKey + "/" + credentialScope
                    + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
            LOG.debug(authorization);
            LOG.debug(xAmzDate);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static byte[] hmacSHA256(String data, byte[] key) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF-8"));
    }

    public static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName)
            throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
        byte[] kDate = hmacSHA256(dateStamp, kSecret);
        byte[] kRegion = hmacSHA256(regionName, kDate);
        byte[] kService = hmacSHA256(serviceName, kRegion);
        byte[] kSigning = hmacSHA256("aws4_request", kService);
        return kSigning;
    }

}

結論

AWSのSDK使える人は使ったほうが早いと思いますし、公式もそれを推奨しています。メンテを考えるとSDK使ったほうが絶対によいです。ただ自前で書いても(ハマらなければ)大した時間かからないので、何らかの理由で自前でやる方はご参考にどうぞ。

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

【学習メモ】AWS SNS

SNS

Simple Notification Service。
フルマネージド型のプッシュ型通知サービス。

SNSはどう言うもの?
TOPICを作成して、どういう受信側に送るのかというpolicyを指定することによって、
特定の通信内容を受信側に送る。

特徴

AWSの他のサービスと連携して、疎結合化が可能。
Ref:https://aws.amazon.com/cn/sns/features/

標準トピック

1回以上受信され順番付けされていないメッセージをアプリケーションが処理できるのであれば、
多くのシナリオで使用することができます。

FIFO トピック

オペレーションやイベントの順番が重要であったり、
重複を許容しないようなアプリケーション間で行うメッセージングを、
強化するために設計されています。

SQS Vs. SNS

Ref:https://medium.com/awesome-cloud/aws-difference-between-sqs-and-sns-61a397bf76c5

◯エンティティタイプ
SQS:キュー
SNS:トピック

◯Message consumption
SQS :プル型(受信側はSQSにてメッセージを引っ張る)
SNS :プッシュ型( SNSはメッセージを受信側に送る)

◯ユーケース
SQS :二つのアプリを疎結合。非同期処理を許す。
SNS :同じメッセージを複数の方法で処理できる。

◯永続性
SQS:永続性あり。
SNS:永続性なし。
メッセージの到着時にどちらのコンシューマーが存在していたとしても、メッセージを取得すると、メッセージは削除される。利用可能なコンシューマーがない場合、メッセージは失う。

※SQSではメッセージ配信は保証されるが、SNSでは保証されていない。

◯消費者タイプ
SQS:すべてのコンシューマーは同一であると想定されているため、メッセージをまったく同じ方法で処理する。
SNS:すべての消費者は(想定される)さまざまな方法でメッセージを処理する。

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

AWS 入門者が EC2 で Linux 仮想サーバーを構築してみた

概要

本項では、クラウドコンピューティングサービスである Amazon Web Service (AWS) を用いて Linux 仮想サーバーを構築する手順を紹介する。

環境

  • MacBook Air (Retina, 13-inch, 2018)
  • macOS Big Sur (Version 11.2.3)
  • メモリ 16 GB

AWS とは

  • クラウドコンピューティングサービスのひとつである
  • 200 以上のサービスがあり、組み合わせによって様々なアプリケーションやインフラを構築できる
  • 社内 LAN など、外部のシステムやネットワークと連結することができる
  • 料金体系がサービスの使用量に従う
  • 学生は AWS Educate でクラウドスキルを無料で学習できる

AWS アカウントの作成

AWS を利用するには AWS アカウントを作成する 必要である。登録時にクレジット/デビットカード番号が求められるが、サポートプランでベーシックプランを選択すれば無料でも使用できる。

image.png

AWS をはじめてみよう

AWS は、マネージメントコンソールを用いることで Web ブラウザの GUI で利用・操作することができる。マネージメントコンソールを開くと、利用可能なサービスの一覧を確認できる。

スクリーンショット 2021-03-14 15.28.02.png

今回は、サーバーに必要なもの(OS やソフトウェアなど)を一式でレンタルすることができる Amazon Elastic Compute Cloud (Amazon EC2) を上記の一覧から選択して、仮想サーバーを構築してみよう。

EC2 を用いた仮想サーバー構築

マネージメントコンソールからEC2 を選択すると、EC2 ダッシュボードが表示される。EC2 では、AWS クラウドに作る仮想サーバーをインスタンスという。インスタンスには「設計図をもとに作成されたもの」という意味があり、EC2 にも仮想サーバーの設計図(仮想イメージ)が用意されている。ここでは仮想イメージからインスタンスを作成する手順を紹介する。

スクリーンショット 2021-03-14 15.36.17.png

STEP1: Amazon マシンイメージ(AMI)を選択する

EC2 ダッシュボードで「インスタンスを起動」を選択すると、仮想イメージの一覧を確認できる。

スクリーンショット 2021-03-14 15.45.44.png

EC2 では仮想イメージのことを Amazon マシンイメージ(AMI) という。同じ AMI から作成されたインスタンスは、設定が全て同じの仮想サーバーである。今回は linux 仮想サーバーを構築するために Amazon Linux 2 AMI (HVM), SSD Volume Type を選択してみよう。

STEP2: インスタンスタイプを選択する

次の画面ではインスタンスの性能が一覧で表示される。ここではインスタンスの CPU やメモリなどを選択できる。性能によって料金体系も異なるが、今回は無料で使用できる t2.micro を選択して、「確認と作成」ボタンをクリックしよう。

スクリーンショット 2021-03-14 16.06.34.png

STEP7: インスタンスタイプを作成する

次の画面で「起動」ボタンを押すとインスタンスが作成される。なお STEP3~STEP6 の各種設定は、インスタンス作成後でも可能である。

スクリーンショット 2021-03-14 16.05.15.png

キーペアの作成

インスタンス作成時にキーペアに関するポップアップが表示される。キーペアはインスタンス接続時の認証に必要な鍵である。ローカルサーバーからの接続にも必要なため、ここで新しいキーペアを作成してくおくとよい。キーペアに適当な名前をつけてダウンロードしておこう。

スクリーンショット 2021-03-14 16.29.14.png

キーペアとはログイン認証時の公開鍵と秘密鍵のペアであり、ここでダウンロードされるのは秘密鍵である。RSA によって暗号化されており、拡張子は pem である。

Demo2021.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAhiJeziiC9SC6TiWQfjOjp6vouPD7QwCdHzOLB/+FMD2/8U52
+F8JDcXHYulMTjyXhADy1WF+TKCgohh07GRPNrGU6LxnWLrTKnLkIPzFV/jnU6k1
GILmXI7yvtfWSC5tA5OUSWNUCeFYq8bh2RXKHVmrJxLYaIO/LoeFUFJou2qOgEA6

(中略)

Y+I9qQKBgBUsxReCPgvdRJ2Ba88muNgxWZnr437m9nESo0MMlDH6lNtd5UVOsNbJ
VDqg55p0Gy3Jepk8XZbpAybLmglyJY0cYU1GaCFQdw/NFtCnX8e7oYUGB20uMe40
Muyp+udJCx0MHP+y5hm80FabKziYF/5dQl2LMSZLgGMLO1gt/1YE
-----END RSA PRIVATE KEY-----

インスタンス作成中

インスタンス作成中は次の画面が表示される。右下の「インスタンスの表示」ボタンを押すとインスタンスの状態を確認できる。
スクリーンショット 2021-03-14 16.30.43.png
次の画面で、ステータスチェックが「初期化しています」から「2/2 のチェックに合格しました」に変わるとインスタンスが作成される。
スクリーンショット 2021-03-14 16.33.40.png

SSH 接続

SSH(Secure Shell) とは、ネットワークに接続された機器(今回はインスタンス)を遠隔操作し、管理するための手段をさす。ここでは MacBook から Amazon EC2 に SSH 接続する方法を紹介する。
まずは、接続先の EC2 インスタンスにチェックを入れて接続ボタンを押してみよう。
スクリーンショット 2021-03-14 17.57.04.png

次の画面で「SSH クライアント」タブを開くと、SSH 接続手順が説明されている。

STEP1: SSH クライアントを開く

Mac に標準搭載されているターミナルを SSH クライアントソフトとして使用する。Windows の場合はこちらの動画で詳しく解説されている。

スクリーンショット 2021-03-14 17.58.57.png

STEP2: プライベートキーファイルを見つける

プライベートキーファイルとはキーペアのうちの秘密鍵をさす。今回は先ほどダウンロードした Demo2021.pem を使用する。ユーザーのホームディレクトリに .ssh フォルダを作成し、新規フォルダに pem ファイルを移動してもよい。

ターミナル
mkdir ~/.ssh
mv Demo2021.pem ~/.ssh
cd ~/.ssh

STEP3: 必要に応じてキーが公開されていないことを確認する

chmod コマンドでキーファイルの権限を設定する。

ターミナル
chmod 400 Demo2021.pem

STEP4: プライベートキーファイルを見つける

次のコマンドを実行すると SSH 接続が完了する。

ターミナル
ssh -i "Demo2021.pem" ec2-user@ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com

次のように表示されれば SSH 接続は成功している。

ターミナル
The authenticity of host 'ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com (18.183.11.87)' can't be established.
ECDSA key fingerprint is SHA256:gyQ0V6rZ8KDgET/yROzqJvUy59Elo9wvkuWoNcqniWI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com,18.183.11.87' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-31-0-200 ~]$ 

パッケージのアップデート

デフォルトでインストールされているパッケージが最新でない場合があるため、次のコマンドでアップデートしておくとよい。(すべて最新版の場合は No packages marked for update と表示される。)
AWS 利用者は一般ユーザーであるため yum を実行できないが、sudo コマンドによってルートユーザーの権限をもつことで実行できる。
yum は Red Hat 系の Linux ディストリビューションで使われているパッケージ管理ソフトである。

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo yum update

Python 3 のダウンロード

次のコマンドで Python3 をダウンロードする。(参考

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo yum install python3 -y

Docker のダウンロード

次のコマンドで Docker をダウンロードする。(参考

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo amazon-linux-extras install docker

SSH 接続の終了

ターミナルで logout と実行する。

参考図書

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

【AWS】EC2 による Linux 仮想サーバーの構築

概要

本項では、クラウドコンピューティングサービスである Amazon Web Service (AWS) を用いて Linux 仮想サーバーを構築する手順を紹介する。

環境

  • MacBook Air (Retina, 13-inch, 2018)
  • macOS Big Sur (Version 11.2.3)
  • メモリ 16 GB

AWS とは

  • クラウドコンピューティングサービスのひとつである
  • 200 以上のサービスがあり、組み合わせによって様々なアプリケーションやインフラを構築できる
  • 社内 LAN など、外部のシステムやネットワークと連結することができる
  • 料金体系がサービスの使用量に従う
  • 学生は AWS Educate でクラウドスキルを無料で学習できる

AWS アカウントの作成

AWS を利用するには AWS アカウントを作成する 必要である。登録時にクレジット/デビットカード番号が求められるが、サポートプランでベーシックプランを選択すれば無料でも使用できる。

image.png

AWS をはじめてみよう

AWS は、マネージメントコンソールを用いることで Web ブラウザの GUI で利用・操作することができる。マネージメントコンソールを開くと、利用可能なサービスの一覧を確認できる。

スクリーンショット 2021-03-14 15.28.02.png

今回は、サーバーに必要なもの(OS やソフトウェアなど)を一式でレンタルすることができる Amazon Elastic Compute Cloud (Amazon EC2) を上記の一覧から選択して、仮想サーバーを構築してみよう。

EC2 を用いた仮想サーバー構築

マネージメントコンソールからEC2 を選択すると、EC2 ダッシュボードが表示される。EC2 では、AWS クラウドに作る仮想サーバーをインスタンスという。インスタンスには「設計図をもとに作成されたもの」という意味があり、EC2 にも仮想サーバーの設計図(仮想イメージ)が用意されている。ここでは仮想イメージからインスタンスを作成する手順を紹介する。

スクリーンショット 2021-03-14 15.36.17.png

STEP1: Amazon マシンイメージ(AMI)を選択する

EC2 ダッシュボードで「インスタンスを起動」を選択すると、仮想イメージの一覧を確認できる。

スクリーンショット 2021-03-14 15.45.44.png

EC2 では仮想イメージのことを Amazon マシンイメージ(AMI) という。同じ AMI から作成されたインスタンスは、設定が全て同じの仮想サーバーである。今回は linux 仮想サーバーを構築するために Amazon Linux 2 AMI (HVM), SSD Volume Type を選択してみよう。

STEP2: インスタンスタイプを選択する

次の画面ではインスタンスの性能が一覧で表示される。ここではインスタンスの CPU やメモリなどを選択できる。性能によって料金体系も異なるが、今回は無料で使用できる t2.micro を選択して、「確認と作成」ボタンをクリックしよう。

スクリーンショット 2021-03-14 16.06.34.png

STEP7: インスタンスタイプを作成する

次の画面で「起動」ボタンを押すとインスタンスが作成される。なお STEP3~STEP6 の各種設定は、インスタンス作成後でも可能である。

スクリーンショット 2021-03-14 16.05.15.png

キーペアの作成

インスタンス作成時にキーペアに関するポップアップが表示される。キーペアはインスタンス接続時の認証に必要な鍵である。ローカルサーバーからの接続にも必要なため、ここで新しいキーペアを作成してくおくとよい。キーペアに適当な名前をつけてダウンロードしておこう。

スクリーンショット 2021-03-14 16.29.14.png

キーペアとはログイン認証時の公開鍵と秘密鍵のペアであり、ここでダウンロードされるのは秘密鍵である。RSA によって暗号化されており、拡張子は pem である。

Demo2021.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAhiJeziiC9SC6TiWQfjOjp6vouPD7QwCdHzOLB/+FMD2/8U52
+F8JDcXHYulMTjyXhADy1WF+TKCgohh07GRPNrGU6LxnWLrTKnLkIPzFV/jnU6k1
GILmXI7yvtfWSC5tA5OUSWNUCeFYq8bh2RXKHVmrJxLYaIO/LoeFUFJou2qOgEA6

(中略)

Y+I9qQKBgBUsxReCPgvdRJ2Ba88muNgxWZnr437m9nESo0MMlDH6lNtd5UVOsNbJ
VDqg55p0Gy3Jepk8XZbpAybLmglyJY0cYU1GaCFQdw/NFtCnX8e7oYUGB20uMe40
Muyp+udJCx0MHP+y5hm80FabKziYF/5dQl2LMSZLgGMLO1gt/1YE
-----END RSA PRIVATE KEY-----

インスタンス作成中

インスタンス作成中は次の画面が表示される。右下の「インスタンスの表示」ボタンを押すとインスタンスの状態を確認できる。
スクリーンショット 2021-03-14 16.30.43.png
次の画面で、ステータスチェックが「初期化しています」から「2/2 のチェックに合格しました」に変わるとインスタンスが作成される。
スクリーンショット 2021-03-14 16.33.40.png

SSH 接続

SSH(Secure Shell) とは、ネットワークに接続された機器(今回はインスタンス)を遠隔操作し、管理するための手段をさす。ここでは MacBook から Amazon EC2 に SSH 接続する方法を紹介する。
まずは、接続先の EC2 インスタンスにチェックを入れて接続ボタンを押してみよう。
スクリーンショット 2021-03-14 17.57.04.png

次の画面で「SSH クライアント」タブを開くと、SSH 接続手順が説明されている。

STEP1: SSH クライアントを開く

Mac に標準搭載されているターミナルを SSH クライアントソフトとして使用する。Windows の場合はこちらの動画で詳しく解説されている。

スクリーンショット 2021-03-14 17.58.57.png

STEP2: プライベートキーファイルを見つける

プライベートキーファイルとはキーペアのうちの秘密鍵をさす。今回は先ほどダウンロードした Demo2021.pem を使用する。ユーザーのホームディレクトリに .ssh フォルダを作成し、新規フォルダに pem ファイルを移動してもよい。

ターミナル
mkdir ~/.ssh
mv Demo2021.pem ~/.ssh
cd ~/.ssh

STEP3: 必要に応じてキーが公開されていないことを確認する

chmod コマンドでキーファイルの権限を設定する。

ターミナル
chmod 400 Demo2021.pem

STEP4: プライベートキーファイルを見つける

次のコマンドを実行すると SSH 接続が完了する。

ターミナル
ssh -i "Demo2021.pem" ec2-user@ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com

次のように表示されれば SSH 接続は成功している。

ターミナル
The authenticity of host 'ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com (18.183.11.87)' can't be established.
ECDSA key fingerprint is SHA256:gyQ0V6rZ8KDgET/yROzqJvUy59Elo9wvkuWoNcqniWI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-18-183-11-87.ap-northeast-1.compute.amazonaws.com,18.183.11.87' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-31-0-200 ~]$ 

パッケージのアップデート

デフォルトでインストールされているパッケージが最新でない場合があるため、次のコマンドでアップデートしておくとよい。(すべて最新版の場合は No packages marked for update と表示される。)
AWS 利用者は一般ユーザーであるため yum を実行できないが、sudo コマンドによってルートユーザーの権限をもつことで実行できる。
yum は Red Hat 系の Linux ディストリビューションで使われているパッケージ管理ソフトである。

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo yum update

Python 3 のダウンロード

次のコマンドで Python3 をダウンロードする。(参考

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo yum install python3 -y

Docker のダウンロード

次のコマンドで Docker をダウンロードする。(参考

ターミナル
[ec2-user@ip-172-31-0-200 ~]$ sudo amazon-linux-extras install docker

SSH 接続の終了

ターミナルで logout と実行する。

参考図書

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

LambdaでAWS利用料のグラフをS3に保存し、Slackに通知する

はじめに

AWSアカウントの管理者になってから、ほぼ毎日Cost Explorerで利用料を確認し、料金の急増がないかをチェックしています。ただ、

  • 手動での確認はやはり手間がかかり、もっと手軽な方法でやりたい
  • 利用料の状況をチームメンバーにも共有し、コストの管理意識を芽生えてもらいたい

というニーズがあり、日々の利用料をSlackに通知するためのLambda関数を実装しました。

参考記事

まず、アーキテクチャとソースコードはこちらの記事を参考し、一部カスタマイズして作りました。(@hayao_kさん、ありがとうございました!)
日々のAWS請求額をグラフ付きでSlackに通知する

ただ、弊社のSlackワークスペースにおいては、ファイルアップロードのための files:write スコープが規則上認められていないため、アーキテクチャを一部変更し、Slackへの直接アップロードではなく、一度S3に保存したうえ、グラフのオブジェクトURLをリンクとしてSlackメッセージに貼り付けるという形にしました。実際のアーキテクチャは以下となります。

lambda.png

  1. CloudWatch Eventsによって指定の時間にLambda関数を呼び出す
  2. CloudWatchから EstimatedCharges のメトリクス情報(データポイント)を取得
  3. 取得したデータポイントに基づき、Lambda関数で利用料のグラフを生成
  4. 生成したグラフをS3バケットに保存 ? ここは参考記事からの変更点
  5. 利用料に関するメッセージを作成してSlackに送る(メッセージにグラフのURLも含める)

成果物のイメージ

Slackには以下のようなメッセージが送られます。
image.png

また、メッセージにあるリンクをクリックすると、ブラウザ上でS3に保存されているグラフが表示されます。
image.png

実装の事前準備

Slack Incoming Webhooksを用意

image.png

S3バケットを用意

任意の端末からS3に保存されているグラフにアクセスしたい場合は、バケットのパブリックアクセスを有効化する必要がありますが、社外からのアクセスを回避したいので、バケットポリシーに弊社事業所のグローバルIPのみ許可します。
image.png

Lambda関数を作成

ランタイムは Python 3.8
実行ロールは基本的な権限(CloudWatchにログ出力するための権限)で新規作成したうえ、再度IAMから当該ロールを編集し、下記2つのポリシーを追加します。

  • CloudWatchReadOnlyAccess(手順2でCloudWatchからメトリクス情報を取得するため)
  • 上記S3バケットへの操作権限(手順4でグラフをバケットに保存するため)

Lambda関数の中身(ソースコード)

バケットやWebhooksなどの情報は適宜書き換えるように。

lambda_function.py
import base64
import boto3
import datetime
import json
import logging
import os
import requests
from botocore.exceptions import ClientError

logger = logging.getLogger()
logger.setLevel(logging.INFO)

client = None


def get_cloudwatch_client():
    global client
    client = boto3.client('cloudwatch', region_name='us-east-1')
    return client


def get_metrics(start_time, end_time, client=None):
    if client is None:
        client = get_cloudwatch_client()
    try:
        response = client.get_metric_statistics(
            Namespace='AWS/Billing',
            MetricName='EstimatedCharges',
            Dimensions=[
                {
                    'Name': 'Currency',
                    'Value': 'USD'
                }
            ],
            StartTime=start_time,
            EndTime=end_time,
            Period=21600,
            Statistics=['Maximum']
        )
    except ClientError as e:
        logger.error("Request failed: %s", e.response['Error']['Message'])
    else:
        return response


def get_image(today, yesterday, diff, metrics, s3ObjKey, client=None):
    if client is None:
        client = get_cloudwatch_client()

    if diff > 0:
        title = ('Current Charges: $' + str(today) +
                 ' / Increased $' + str(diff) + ' from Yesterday')
        min = yesterday * 0.7
        max = today * 1.05
    else:
        title = ('Current Charges: $' + str(today))
        min = today * 0.7
        max = yesterday * 1.05

    widget_definition = json.dumps(
        {
            "width": 1280,
            "height": 800,
            "start": "-PT144H",
            "end": "PT0H",
            "timezone": "+0900",
            "view": "timeSeries",
            "stacked": True,
            "stat": "Maximum",
            "title": title,
            "metrics": [
                ["AWS/Billing", "EstimatedCharges", "Currency", "USD"]
            ],
            "period": 43200,
            "annotations": {
                "horizontal": [
                    {
                        "label": "Today",
                        "value": today
                    },
                    {
                        "label": "Yesterday",
                        "value": yesterday
                    }
                ]
            },
            "yAxis": {
                "left": {
                    "label": "$",
                    "min": (min),
                    "max": (max)
                }
            },
        }
    )

    try:
        response = client.get_metric_widget_image(
            MetricWidget=widget_definition
        )
    except ClientError as e:
        logger.error("Request failed: %s", e.response['Error']['Message'])
    else:
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('xxxxxxxxxx')
        res = bucket.put_object(Body=(
            response["MetricWidgetImage"]), Key=s3ObjKey, ContentType='image/png')
        logger.info("Upload Image succeeded.")


def build_payload(today_charge, diff, s3ObjUrl):
    payload = {
        "attachments": [
            {
                "color": "good",
                "title": "AWS料金(クリックしてグラフを確認)",
                "title_link": s3ObjUrl,
                "fields": [
                    {
                        "title": "対象AWSアカウント",
                        "value": "`xxxxxxxxxxxx`",
                        "short": False
                    },
                    {
                        "title": "本日まで利用した金額",
                        "value": ":heavy_dollar_sign:" + str(today_charge),
                        "short": True
                    },
                    {
                        "title": "昨日よりの増加量",
                        "value": ":heavy_dollar_sign:" + str(diff),
                        "short": True
                    }
                ]
            }
        ]
    }
    return payload


def lambda_handler(event, context):
    start_time = datetime.datetime.now() - datetime.timedelta(hours=30)
    end_time = datetime.datetime.now()
    logger.info('Metric Start Time: ' + str(start_time))
    logger.info('Metric End Time: ' + str(end_time))

    metrics = get_metrics(start_time, end_time)
    sorted_data = sorted(metrics['Datapoints'], key=lambda x: x['Timestamp'])
    logger.info("Sorted Data: %s", sorted_data)

    today_charge = sorted_data[-1]['Maximum']
    yesterday_charge = sorted_data[0]['Maximum']
    diff = round(today_charge - yesterday_charge, 2)

    targetAccount = 'xxxxxxxxxxxx'
    s3ObjKey = targetAccount + '/charge-graph-' + \
        str((end_time + datetime.timedelta(hours=9)).date())
    s3ObjUrl = 'https://xxxxxxxx.s3-ap-northeast-1.amazonaws.com/' + s3ObjKey
    get_image(today_charge, yesterday_charge, diff, metrics, s3ObjKey)

    payload = build_payload(today_charge, diff, s3ObjUrl)
    req = requests.post(
        'https://hooks.slack.com/services/xxxxxxxxxxxxxxxx', data=json.dumps(payload))
    try:
        req.raise_for_status()
        logger.info("Message posted.")
        return req.text
    except requests.RequestException as e:
        logger.error("Request failed: %s", e)
    return req

最後に

Lambdaを実装し、データポイントの取得やグラフ(ウイジェット)生成のためのパラメータをチューニングして簡単なテストを済ませ、CloudWatchのイベントルールを設定したら終わりです。これで毎日Slackで料金のチェックや推移グラフを見ることができるようになりました。かなり便利です!

後日に余裕あったら get_metric_statisticsget_metric_widget_image の仕様をまとめてみます。

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

[EKS] Cronjob経由でpodからAWSリソースへのアクセスとkubectlを実行したい

はじめに

Kubernetesの Cronjob経由のPodでkubectlコマンドを実行したり、AWSリソースへアクセスする方法についてのメモです。

やりたいことは以下の通りです。

  • 定期的にkubectlコマンドを実行し、結果をAWSのcloudwatchのカスタムメトリクスへ送信する

ポイントは以下の5点です

  • aws cliとkubectlを実行できるコンテナイメージの作成
  • awsリソースを操作する権限
  • kubectlリソースを操作する権限
  • cloud watchへカスタムメトリクスの送信
  • cron jobの実行

aws cliとkubectlを実行できるコンテナイメージの作成

aws cliはamazonに公式のイメージがあります。
https://hub.docker.com/r/amazon/aws-cli
これにkubectlを追加するのが良さそうです。

FROM amazon/aws-cli:latest

RUN yum update -y \
    && curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" \
    && chmod +x kubectl \
    && mv kubectl  /usr/local/bin/ 

ENTRYPOINT [""]

kubectlだけ実行したい人はalpineにkubectlを追加した方がイメージが軽くなると思います。
※最初にこの方法で進めてたら、aws cliはalpineをサポート対象に含まれていないのと、
動かすにはpythonやらglibcやらと結局イメージ膨れそうなので、amazonの公式イメージにしました。

FROM alpine

RUN apk add --no-cache --virtual=build wget \
    && wget https://storage.googleapis.com/kubernetes-release/release/v1.20.0/bin/linux/amd64/kubectl \
    && mv kubectl /usr/local/bin/ \
    && chmod +x /usr/local/bin/ \
    && apk del build

各イメージサイズはこんな感じです。

# docker images | sort                                                                                                                                              master
REPOSITORY             TAG       IMAGE ID       CREATED         SIZE
alpine                 latest    28f6e2705743   2 weeks ago     5.61MB
alpine-with-kubectl    latest    0f52e434f905   6 minutes ago   45.9MB
amazon/aws-cli         latest    19790bab4269   4 days ago      298MB
aws-cli-with-kubectl   latest    f7b960da07f6   3 hours ago     534MB

ECRへpushしておく

# aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
# docker build -t xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/nodestatus-check:latest .
# docker push xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/nodestatus-check 

awsリソースを操作する権限

これはNodeに付与するIAMRoleで制御できました。

kubectl/EKSリソースを操作する権限

そのまま試したらNGでした。
defaultのservice accountが使われるようで、そのアカウントに権限がないみたいです。

kubectl run -it aws-cli-test --image=amazon/aws-cli:latest --command  '/bin/bash'
bash-4.2# 
bash-4.2# curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" \
>     && chmod +x kubectl \
>     && mv kubectl  /usr/local/bin/ 
bash-4.2# kubectl get node
Error from server (Forbidden): nodes is forbidden: User "system:serviceaccount:default:default" cannot list resource "nodes" in API group "" at the cluster scope

新たにservice accountを作成して、rbacのcluster role bindingに追加してあげる

# kubectl create sa aws-cli
serviceaccount/aws-cli created

# kubectl edit clusterrolebinding -n kube-system cluster-admin

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
~~
- kind: ServiceAccount
  name: aws-cli
  namespace: default

そうすると見えるようになる。
ただ、 軽く調べたらOpenID Connect (OIDC) ID を使うのが正攻法みたいです。

# kubectl run -it aws-cli-test --image=amazon/aws-cli:latest --command '/bin/bash' --serviceaccount=aws-cli
If you don't see a command prompt, try pressing enter.
bash-4.2# 
bash-4.2# kubectl get node
NAME                                            STATUS   ROLES    AGE   VERSION
ip-10-0-2-99.ap-northeast-1.compute.internal    Ready    <none>   18d   v1.17.12-eks-7684af
ip-10-0-3-144.ap-northeast-1.compute.internal   Ready    <none>   18d   v1.17.12-eks-7684af
bash-4.2# kubectl get pod | grep aws-cli     
aws-cli-test                                      1/1     Running   0          2m25s

cloud watchへカスタムメトリクスの送信

aws cliが使えれば送れます。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudwatch-custom-metrics/

因みにやりたかったことは、NodeのステータスがたまにNotReadyになってて、何か気づく術が欲しかったのが発端です。
AWSへ問い合わせたら、prometheus入れて kube-state-metrics 入れたらメトリクス取れるよ。と言われましたが、途中で断念しました。

nodestatus=$(kubectl get node | grep Ready | wc -l)
aws cloudwatch put-metric-data --metric-name notready_count --dimensions ClusterName=eks-cluster --namespace "ContainerInsights" --value $nodestatus

cron jobの実行

config mapで実行したいscriptを書いて、cronjob側で読み込みます。

---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: nodecheck
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: aws-cli
          containers:
          - name: nodecheck
            image: xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/nodestatus-check:latest
            imagePullPolicy: IfNotPresent
            command: ["/bin/sh"]
            args: ["/root/entrypoint.sh"]
            volumeMounts:
              - name: entrypoint
                mountPath: /root
          restartPolicy: OnFailure
          volumes:
            - name: entrypoint
              configMap:
                name: node-status-check
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-status-check
data:
  entrypoint.sh: |-
    #!/bin/sh
    nodestatus=$(kubectl get node | grep Ready | wc -l)
    aws cloudwatch put-metric-data --metric-name Eks_node_notready_count --dimensions ClusterName=eks-cluster --namespace "ContainerInsights" --value $nodestatus
---

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

生のPHPとLaravelの、二通りの類似ポートフォリオを作った話

はじめに

こんにちは、おーもとと申します。エンジニアに転職をするため学習している初学者です。
私は車が好きで、「近年の若者の車離れ」という問題にフォーカスしたアプリを制作しようと思いました。

制作背景

若者が車を持たない理由には様々な理由があると思いますが、
「欲しいと思えるほど魅力を感じる車に出会っていないからなのでは?」
と思い、
・かわいいやかっこいいというスタイル
・大きさ
・国産か外車か
・アウトドアや街乗りという用途
これらの項目に当てはまる車を、結果として表示するアプリを制作することにしました。
(これらの特徴は全て私が定めているため、投票などにより特徴を決める機能をつけたいです)

11月 PHPでアプリ開発

10月からPHPの学習を始めていたので、そのアプリはPHPで制作しました。
カーセンサーAPIを使用して、車の情報を取得します。
解説動画:https://www.youtube.com/watch?v=ZXbgUtjxKM8
スクリーンショット 2020-12-12 12.34.26.png

機能

ユーザー登録関連
⚪︎ ログイン
⚪︎ ログアウト
⚪︎ 新規登録
⚪︎ ユーザー件数を表示

車の検索機能
⚪︎ 車の見た目→「かわいい」「かっこいい」「シンプル」「おしゃれ」「レトロ」
⚪︎ 車のサイズ→「ふつう」「すごくおおきい」「おおきい」「ちいさい」
⚪︎ 車の製造国→「国産車」「外車」
⚪︎ 車の用途 →「街乗り」「アウトドア」「スポーツ」

カーセンサーAPI連携
⚪︎ DBにある車情報と合致した車情報を取得
⚪︎ cronでキャッシュファイル自動生成

苦労した点

検索結果の画像表示高速化

検索の度にAPIからデータを取得していたので、電波の悪い場所では結果の表示に1分以上かかっていました。
毎日APIからデータを自動取得しキャッシュ化することで、ユーザーにストレスのない速度で結果を表示させることができました。

EC2へデプロイ

公式ドキュメントを参考にしデプロイしました。
その際、インフラの知識が不足していたため、デプロイに一週間以上かかりました。

APIのサービス終了!!

転職活動を始めようとした際、一週間後にカーセンサーAPIサービスが終了すると知りました。
急いで提供元へ問い合わせたところ、
「完全に提供が終了すること」「24時間以上のキャッシュデータの保有も禁止」、ということを告げられました。
その後、他の車データAPIの提供元を調べましたが他にありませんでした。
画像だけでもどうにかならないかと思い、ト◯タや◯産などの画像利用規約を確認しましたが、
営利目的ではない&提供元のURLなどの情報を記載する
としても、利用は禁止でした。
そのためLaravelの勉強も兼ねて、画像問題を解決できるアプリの制作に取り掛かりました。

1月 Laravelでアプリ開発

12月末からLaravelの学習を始め、1月からアプリの制作に取り掛かりました。

前回のPHPで制作したポートフォリオとの違い

画像の取得にAPIを用いていましたが、ユーザーから愛車の画像を提供してもらう方針に変更し、機能の追加などを行いました。

完成

アプリのURL:https://pf-kurushira.com
(スマホサイズにも対応しています)
スクリーンショット 2021-03-14 16.03.00.png

使用技術

使用言語

⚪︎ HTML
⚪︎ CSS
⚪︎ SCSS
⚪︎ PHP 7.4.14
⚪︎ Laravel 6.20.11

インフラ

⚪︎ Github Actions 自動デプロイ
⚪︎ Docker 20.10.2 / docker-compose 1.27.4
⚪︎ nginx 1.18
⚪︎ mysql 5.7.31 / PHPMyAdmin
⚪︎ AWS ( EC2, ALB, ACM, S3, RDS, Route53, VPC, EIP, IAM)

インフラ構成図

スクリーンショット 2021-02-28 20 11 32

機能一覧

機能 概要
ユーザー管理機能 新規登録・ログイン・ログアウトができます
簡単ログイン機能 ログイン画面のゲストログインをクリックすることで、ゲストユーザーとしてログインできます
おすすめ車種検索機能 条件を選択すると、それにあった車種一覧を表示します
検索履歴機能 直近の検索履歴・結果を表示します
画像提供機能 ユーザーの所有している車の画像を提供できます
提供した画像の削除機能 提供した画像を削除できます
提供画像一覧表示機能 自身が提供した画像一覧を表示します。
ユーザー情報編集機能 ご登録いただいたユーザー名・メールアドレスを変更できます
Twitterシェア機能 車の検索結果をツイートすることができます
レスポンシブ機能 スマホサイズ(320~540px)にも対応しています

DB設計

スクリーンショット 2021-02-20 19 05 58

各テーブルについて

テーブル名 説明
users 登録ユーザー情報
cars 登録車情報
histories 直近の検索結果の情報
car_images 提供画像の情報

苦労した点

ユーザー情報編集ページのバリデーション

LaravelのAuth機能のバリデーションを使いまわそうとしましたが、ブラックボックスになっていて苦労しました。
→新しくバリデーションを作成。

S3からオブジェクト削除

画像の削除機能でDBからだけでなく、S3からもオブジェクトを削除する必要があり苦労しました。
→解決方法を記事にしました
laravel6でS3に画像アップロード&削除

今後の課題

機能

機能 概要
英訳機能 Google Cloud Translation APIを利用して翻訳
通報機能 ユーザーの投票で不適切な画像を削除

技術

⚪︎ テスト
⚪︎ Dockerを用いた本番環境の構築
⚪︎ ECSへデプロイ

参考にした学習教材など

PHP/Laravel

【Udemy】PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門
【書籍】詳細!PHP 7+MySQL 入門ノート
【書籍】PHPフレームワークLaravel入門 第2版
【書籍】PHPフレームワーク Laravel実践開発
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る

AWS

【Udemy】AWS:ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得

Docker

【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン

さいごに

生のPHPでひとつPFを制作したのは、基礎力が身についたので良かったと思います。
今回ポートフォリオ完成を優先したため、ECSではなくEC2へデプロイしました。
まだ課題も多いですが、ブラッシュアップしていきたく思っています。
長くなりましたが、ここまで読んでくださりありがとうございました!!

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

AWS CLIからEC2インスタンスを起動/停止してみた。超ざっくりまとめてみた。

EC2オンデマンドインスタンスの場合、利用しない時間は停止し、利用するときだけ起動するように制御をしたいものです。
このため、AWS CLIからEC2インスタンスの起動/停止を行うためのやり方を調べ、自分の備忘録としてまとまました。これを見れば、一目で思い出せるはず!
qiita-square


前提

  • 説明を極力シンプルにするために、CLI用IAMユーザーの登録、AWS CLIのインストール(+設定:aws configure)は完了している状態から始めます。
    • IAMユーザーの作り方は公式サイトを参照してください。
    • 登録したユーザには、アクセス権限のポリシー「AmazonEC2FullAccess」を付けておきます。(今回だけならフルには不要なのですが)

起動

実行コマンド
aws ec2 start-instances --instance-ids "i-xxxxxxxxxxxxxxxxx"
処理結果
{
    "StartingInstances": [
        {
            "InstanceId": "i-xxxxxxxxxxxxxxxxx", 
            "CurrentState": {
                "Code": 0, 
                "Name": "pending"
            }, 
            "PreviousState": {
                "Code": 80, 
                "Name": "stopped"
            }
        }
    ]
}

起動完了待ち

実行コマンド
aws ec2 wait instance-running --instance-ids "i-xxxxxxxxxxxxxxxxx"

停止

実行コマンド
aws ec2 stop-instances --instance-ids "i-xxxxxxxxxxxxxxxxx"
処理結果
{
    "StoppingInstances": [
        {
            "InstanceId": "i-xxxxxxxxxxxxxxxxx", 
            "CurrentState": {
                "Code": 64, 
                "Name": "stopping"
            }, 
            "PreviousState": {
                "Code": 16, 
                "Name": "running"
            }
        }
    ]
}

状態表示(停止中)

停止状態でdescribe-instance-statusすると、結果のjsonが空値で返ってきます。

実行コマンド
aws ec2 describe-instance-status --instance-ids "i-xxxxxxxxxxxxxxxxx"
処理結果
{
    "InstanceStatuses": []
}

さいごに

この記事はAWS初学者を導く体系的な動画学習サービス「AWS CloudTech」の課題カリキュラムで作成しました。

このサービスは、テンポの良い/わかりやすい動画説明をもとに、気軽に実践を積み、自分の血肉とできるオンラインスクールです。
コミュニティも存在し、Slackで会員通しの情報交換/質問も気楽にできます。
qiita-square

書籍を購入するような値段で学習ができ、とてもお得です。(個人的な感想です)
image.png

では、また次回お会いしましょう!

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

【AWS】 S3簡単まとめ

S3 (Simple Storage Serviceの略)

AWSのサービスの1つで、クラウド型のオブジェクトサービスです。
容量は無制限で、破格の安さと高い耐久性もあり、高可用性を兼ね備えています。

(補足)
※1 オブジェクトサービス
 オブジェクト(ファイル、テキスト、データなど)単位で出し入れが可能なストレージです。

※2 ストレージ
パソコンのデータを保管しておくための補助記憶装置のことです。

メリット

● ストレージ機能

ストレージの容量により自動的に拡張・縮小されます。
大量のデータを取り扱う際にも最適です。

  

● 耐久性と可用性の高さ

全てのオブジェクトに対して99.999999999%の耐久性と、可用性においても、1年間で99.99%になるように設計されています。
可用性の99.99%というのは、1年間でアクセスできない時間が52〜53分程度なので、高可用性であると言えます。障害などにも強いため、データを安全に利用することが可能です。

  

● 低コスト

1GBあたり0.025ドル(日本円で2.72円、3/14時点)と大変安く、ストレージにおいの容量、リクエスト数、データの転送数などで料金が決まるシステムです。
つまり、最低料金や一定額の料金のお支払いがありませんので、使用量に応じた金額になります。

  

● 静的ファイルの配信

静的なコンテンツを配信する場合には、S3から直接公開することができます。
アクセスによる負荷の軽減に繋がります。

S3のイメージ

[S3(2).png]

バケット
S3に保存されるあらゆるオブジェクトを入れておく箱のことです。

キー
ファイルなどが持つ固有の識別子(名前)のことです。

=> 画像やテキストデータがそれぞれURLを持ち、公開閲覧することができます。

終わり

最後までご覧いただきまして、ありがとうございます。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

サーバーの構築からソースコードのデプロイまで(Amazon Linux 2, Nginx + Puma, Rails 6.1, Ruby2.7.2)

この記事での注意

この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

サーバーにインストールする環境

  • Amazon Linux 2
  • Ruby 2.7.2 (rbenv)
  • Rails 6.1
  • MySQL or PostgreSQL
  • Nginx + Puma 5
  • Node.js 12
  • Yarn

本番サーバーの構築

本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

1. システムライブラリアップデート

まずはAmazon Linux 2のシステムライブラリを更新します。

サーバー
$ sudo yum update -y

2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8

3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

サーバー
$ yum info chrony
$ chronyc sources -v

4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git

5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

  • PostgreSQLの場合
サーバー
$ sudo yum -y install postgresql-devel
  • MySQLの場合
サーバー
$ sudo yum -y install mysql-devel

6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

6.1 rbenvのインストール

サーバー
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v

6.2 ruby-buildのインストール

サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2

rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

7. Rails 6で必要なモジュールのインストール

7.1 Node.jsのインストール

Rails 6からは Node.jsYarn が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node

7.2 Yarnのインストール

サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y

8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除)

$ bundle install
$ bundle exec rails -v
(インストールされたことを確認)

$ cd ..
$ rm -rf rails

データベースの設定

9. 本番データベースの作成

ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

  1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
  2. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は config/credentials.yml.enc にセットします。(Rails5.2以降で可能)

このファイルはエディタで表示すると暗号化された文字列になっていますが、 master.key を使うことで閲覧、編集することができます。この credentials.yml.encmaster.key の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

ローカル
$ EDITOR=vim bin/rails credentials:edit

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。

credential.yml.enc(ローカル)
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

database.yml(ローカル)
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は 5432、MySQLの場合は 3306 になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master

デプロイ設定

デプロイツールCapistranoを使ったデプロイの設定を行います。

11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

Gemfile(ローカル)
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
ローカル
$ bundle install
$ bundle exec cap install

12. Capfileの設定

下記のようにCapfileに追記します。

Capfile(ローカル)
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :

13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

config/deploy.rb(ローカル)
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5

linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の (server ip) にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

config/deploy/production.rb(ローカル)
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}

区切りが良いので、ここでいったんコミット、プッシュしましょう。(プッシュは任意です)

ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master

14. Githubへのキー登録

デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置(デプロイ)を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)

エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
(ローカルにあるmaster.keyを貼り付け)

これで問題は解決しましたので、再度、デプロイチェックを行います。

ローカル
$ bundle exec cap production deploy:check

成功するはずです。これでデプロイ先ディレクトリができました。

16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

ローカル
$ bundle exec cap production deploy

最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production

(最新リリース)は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

ローカル
$ bundle exec cap production deploy

これで成功するはずです。

WebサーバーとRailsアプリの連携

17. Pumaの設定

本番環境のPumaの設定を行います。

config/puma/production.rb(ローカル)
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master

18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

サーバー
$ sudo amazon-linux-extras install nginx1 -y

19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
/etc/nginx/conf.d/sample611.conf(サーバー)
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

upstream puma内にあるpuma.sockのパスに気をつけてください。これは15のbindで設定したパスにあわせなければなりません。

20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

サーバー
$ sudo vim /etc/systemd/system/puma.service
/etc/systemd/system/puma.service(サーバー)
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target

ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

サーバー
$ sudo chmod +x /etc/systemd/system/puma.service

これでサーバー側の設定は終わりです。

21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma

下記のコマンドで起動を確認します。

サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]

上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

サーバー
$ sudo systemctl start nginx.service

これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://(サイトのIP)/

21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

config/deploy.rb(ローカル)
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end

これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

ローカル
$ git add .
$ git commit -m "Setup restart puma"

再起動することを確認するため、デプロイします。

ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s

デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です!お疲れ様でした。

puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service

以上です。

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

Rails 6アプリをAWSへのデプロイする手順

この記事での注意

この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

サーバーにインストールする環境

  • Amazon Linux 2
  • Ruby 2.7.2 (rbenv)
  • Rails 6.1
  • MySQL or PostgreSQL
  • Nginx + Puma 5
  • Node.js 12
  • Yarn

本番サーバーの構築

本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

1. システムライブラリアップデート

まずはAmazon Linux 2のシステムライブラリを更新します。

サーバー
$ sudo yum update -y

2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8

3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

サーバー
$ yum info chrony
$ chronyc sources -v

4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git

5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

  • PostgreSQLの場合
サーバー
$ sudo yum -y install postgresql-devel
  • MySQLの場合
サーバー
$ sudo yum -y install mysql-devel

6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

6.1 rbenvのインストール

サーバー
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v

6.2 ruby-buildのインストール

サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2

rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

7. Rails 6で必要なモジュールのインストール

7.1 Node.jsのインストール

Rails 6からは Node.jsYarn が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node

7.2 Yarnのインストール

サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y

8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除)

$ bundle install
$ bundle exec rails -v
(インストールされたことを確認)

$ cd ..
$ rm -rf rails

データベースの設定

9. 本番データベースの作成

ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

  1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
  2. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は config/credentials.yml.enc にセットします。(Rails5.2以降で可能)

このファイルはエディタで表示すると暗号化された文字列になっていますが、 master.key を使うことで閲覧、編集することができます。この credentials.yml.encmaster.key の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

ローカル
$ EDITOR=vim bin/rails credentials:edit

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。

credential.yml.enc(ローカル)
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

database.yml(ローカル)
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は 5432、MySQLの場合は 3306 になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master

デプロイ設定

デプロイツールCapistranoを使ったデプロイの設定を行います。

11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

Gemfile(ローカル)
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
ローカル
$ bundle install
$ bundle exec cap install

12. Capfileの設定

下記のようにCapfileに追記します。

Capfile(ローカル)
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :

13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

config/deploy.rb(ローカル)
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5

linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の (server ip) にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

config/deploy/production.rb(ローカル)
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}

区切りが良いので、ここでいったんコミット、プッシュしましょう。(プッシュは任意です)

ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master

14. Githubへのキー登録

デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置(デプロイ)を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)

エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
(ローカルにあるmaster.keyを貼り付け)

これで問題は解決しましたので、再度、デプロイチェックを行います。

ローカル
$ bundle exec cap production deploy:check

成功するはずです。これでデプロイ先ディレクトリができました。

16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

ローカル
$ bundle exec cap production deploy

最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production

(最新リリース)は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

ローカル
$ bundle exec cap production deploy

これで成功するはずです。

WebサーバーとRailsアプリの連携

17. Pumaの設定

本番環境のPumaの設定を行います。

config/puma/production.rb(ローカル)
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master

18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

サーバー
$ sudo amazon-linux-extras install nginx1 -y

19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
/etc/nginx/conf.d/sample611.conf(サーバー)
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

upstream puma内にあるpuma.sockのパスに気をつけてください。これは15のbindで設定したパスにあわせなければなりません。

20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

サーバー
$ sudo vim /etc/systemd/system/puma.service
/etc/systemd/system/puma.service(サーバー)
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target

ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

サーバー
$ sudo chmod +x /etc/systemd/system/puma.service

これでサーバー側の設定は終わりです。

21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma

下記のコマンドで起動を確認します。

サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]

上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

サーバー
$ sudo systemctl start nginx.service

これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://(サイトのIP)/

21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

config/deploy.rb(ローカル)
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end

これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

ローカル
$ git add .
$ git commit -m "Setup restart puma"

再起動することを確認するため、デプロイします。

ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s

デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です!お疲れ様でした。

puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service

以上です。

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

AWS EC-2 docker + wordpress インストール

AWSにEC2作る

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

ec2でdockerとwordpressをインストール

sshでログイン


       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-x-x-x-x ~]$

yumをアップデート

[ec2-user@ip-x-x-x-x ~]$ sudo yum update -y
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                           | 3.7 kB  00:00:00
No packages marked for update

docker準備

dockerをインストール

sudo amazon-linux-extras install docker -y
[ec2-user@ip-172-31-20-108 ~]$ sudo amazon-linux-extras install docker -y
Installing docker
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
Cleaning repos: amzn2-core amzn2extra-docker
10 metadata files removed
4 sqlite files removed
0 metadata files removed
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                           | 3.7 kB  00:00:00
amzn2extra-docker                                                                                                                                    | 3.0 kB  00:00:00
(1/5): amzn2-core/2/x86_64/group_gz                                                                                                                  | 2.5 kB  00:00:00
(2/5): amzn2extra-docker/2/x86_64/updateinfo                                                                                                         |   76 B  00:00:00
(3/5): amzn2extra-docker/2/x86_64/primary_db                                                                                                         |  75 kB  00:00:00
(4/5): amzn2-core/2/x86_64/updateinfo                                                                                                                | 350 kB  00:00:00
(5/5): amzn2-core/2/x86_64/primary_db                                                                                                                |  50 MB  00:00:00
Resolving Dependencies
--> Running transaction check
---> Package docker.x86_64 0:19.03.13ce-1.amzn2 will be installed
--> Processing Dependency: runc >= 1.0.0 for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: containerd >= 1.3.2 for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: pigz for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: libcgroup for package: docker-19.03.13ce-1.amzn2.x86_64
--> Running transaction check
---> Package containerd.x86_64 0:1.4.1-2.amzn2 will be installed
---> Package libcgroup.x86_64 0:0.41-21.amzn2 will be installed
---> Package pigz.x86_64 0:2.3.4-1.amzn2.0.1 will be installed
---> Package runc.x86_64 0:1.0.0-0.1.20200826.gitff819c7.amzn2 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================================================================================
 Package                           Arch                          Version                                                     Repository                                Size
============================================================================================================================================================================
Installing:
 docker                            x86_64                        19.03.13ce-1.amzn2                                          amzn2extra-docker                         37 M
Installing for dependencies:
 containerd                        x86_64                        1.4.1-2.amzn2                                               amzn2extra-docker                         24 M
 libcgroup                         x86_64                        0.41-21.amzn2                                               amzn2-core                                66 k
 pigz                              x86_64                        2.3.4-1.amzn2.0.1                                           amzn2-core                                81 k
 runc                              x86_64                        1.0.0-0.1.20200826.gitff819c7.amzn2                         amzn2extra-docker                        3.7 M

Transaction Summary
============================================================================================================================================================================
Install  1 Package (+4 Dependent packages)

Total download size: 65 M
Installed size: 270 M
Downloading packages:
(1/5): libcgroup-0.41-21.amzn2.x86_64.rpm                                                                                                            |  66 kB  00:00:00
(2/5): pigz-2.3.4-1.amzn2.0.1.x86_64.rpm                                                                                                             |  81 kB  00:00:00

(省略)

dockerサービス起動

[ec2-user@ip-x-x-x-x ~]$ sudo systemctl start docker

dockerサービス状態確認

[ec2-user@ip-x-x-x-x ~]$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-03-14 05:59:36 UTC; 25s ago
     Docs: https://docs.docker.com
  Process: 3722 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS)
  Process: 3709 ExecStartPre=/bin/mkdir -p /run/docker (code=exited, status=0/SUCCESS)
 Main PID: 3731 (dockerd)
    Tasks: 8
   Memory: 36.6M
   CGroup: /system.slice/docker.service
           mq3731 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=1024:4096

Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354394783Z" level=info msg="scheme \"unix\" not registered, f...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354629298Z" level=info msg="ccResolverWrapper: sending update...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354858217Z" level=info msg="ClientConn switching balancer to ...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.394039324Z" level=info msg="Loading containers: start."
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.858998545Z" level=info msg="Default bridge (docker0) is assig...ddress"
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.915341056Z" level=info msg="Loading containers: done."
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.942822332Z" level=info msg="Docker daemon" commit=4484c46 gra...3.13-ce
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.943274164Z" level=info msg="Daemon has completed initialization"
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal systemd[1]: Started Docker Application Container Engine.
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.967245548Z" level=info msg="API listen on /var/run/docker.sock"
Hint: Some lines were ellipsized, use -l to show in full.

dockerをec2-userで実行可能にする

[ec2-user@ip-x-x-x-x ~]$ sudo usermod -a -G docker ec2-user
[ec2-user@ip-x-x-x-x ~]$ exit

※ここで一度ログアウトして再度ログインすることで権限が反映される

docker-compose

docker-composeインストール

[ec2-user@ip-x-x-x-x ~]$ sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   633  100   633    0     0   9447      0 --:--:-- --:--:-- --:--:--  9447
100 11.6M  100 11.6M    0     0  22.2M      0 --:--:-- --:--:-- --:--:-- 44.5M

[ec2-user@ip-x-x-x-x ~]$ sudo chmod +x /usr/local/bin/docker-compose

curlのURLは↓ここでdocker-composeのバージョンを確認して適宜書き換える。(2021/03/14時点は1.28.5)
https://docs.docker.com/compose/install/#install-compose-on-linux-systems

バージョン確認

[ec2-user@ip-x-x-x-x ~]$ docker-compose --version
docker-compose version 1.28.5, build c4eb3a1f

wordpress

docker-compose.ymlファイルを用意

[ec2-user@ip-x-x-x-x ~]$ pwd
/home/ec2-user
[ec2-user@ip-x-x-x-x ~]$ mkdir wordpress
[ec2-user@ip-x-x-x-x ~]$ vi ~/wordpress/docker-compose.yml
docker-compose.yml
version: '3.1'

services:

  wordpress:
    container_name: wp-trial
    image: wordpress
    restart: always
    ports:
      - 55555:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    container_name: wp-db
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

wordpressをビルドして起動

docker-compose.ymlが入っているディレクトリに移動してdocker-compose upする。

[ec2-user@ip-x-x-x-x ~]$ cd wordpress/
[ec2-user@ip-x-x-x-x wordpress]$ pwd
/home/ec2-user/wordpress
[ec2-user@ip-x-x-x-x wordpress]$ ls -la
total 4
drwxrwxr-x 2 ec2-user ec2-user  32 Mar 14 06:12 .
drwx------ 5 ec2-user ec2-user 142 Mar 14 06:12 ..
-rw-rw-r-- 1 ec2-user ec2-user 638 Mar 14 06:12 docker-compose.yml
[ec2-user@ip-x-x-x-x wordpress]$ docker-compose up --build -d
Creating network "wordpress_default" with the default driver
Creating volume "wordpress_wordpress" with default driver
Creating volume "wordpress_db" with default driver
Pulling wordpress (wordpress:)...
latest: Pulling from library/wordpress
6f28985ad184: Pull complete
db883aae18bc: Pull complete
ffae70ea03a9: Pull complete
1e8027612378: Pull complete
3ec32e53dce5: Pull complete
3bb74037bf77: Pull complete
feda0fbd85b1: Pull complete
b2244185b327: Pull complete
8852ae668073: Pull complete
985e21deb66e: Pull complete
f262da4e7afa: Pull complete
157f3d683e13: Pull complete
990684a56233: Pull complete
c80999e49328: Pull complete
02585da80b89: Pull complete
d68ab7635a0a: Pull complete
5a577fb48682: Pull complete
d27e8a2c96b8: Pull complete
f94fa08d2764: Pull complete
6db2ebd4cef9: Pull complete
9c4c1399bbb1: Pull complete
Digest: sha256:92fb18e472ce46f289b475457289075c20387374d79bc41724689aa88112eab1
Status: Downloaded newer image for wordpress:latest
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
6f28985ad184: Already exists
e7cd18945cf6: Pull complete
ee91068b9313: Pull complete
b4efa1a4f93b: Pull complete
f220edfa5893: Pull complete
74a27d3460f8: Pull complete
2e11e23b7542: Pull complete
39ac93d44c47: Pull complete
dfd9db50d4ea: Pull complete
4e97f54f11a3: Pull complete
ebfb95795c5f: Pull complete
Digest: sha256:5f649e87093a5b6b863f5c5277b2d2aa797b04d68657494e0f28ffabfa25e781
Status: Downloaded newer image for mysql:5.7
Creating wp-trial  ... done
Creating wp-db ... done
[ec2-user@ip-172-31-20-108 wordpress]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                   NAMES
d0296aeb5318        mysql:5.7           "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp, 33060/tcp     wp-db
c01446d87997        wordpress           "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:55555->80/tcp   wp-trial

Wordpressにアクセスしてみる

image.png


参考

AWS公式 Dockerインストール手順
AWS EC2インスタンスにdockerとdocker-composeをインストールして簡単なWEBサービスを立ち上げる方法

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

AWS EC2 docker + wordpress インストール

AWSにEC2作る

AMIの選択

image.png

インスタンスタイプの選択

image.png

インスタンスの詳細の設定

自動割り当てパブリックIPを「有効」にした以外はデフォルト設定。

image.png

ストレージの追加

image.png

タグの追加

タグはなにも設定していない。

image.png

セキュリティグループの設定

セキュリティグループは事前に作成してあったものを利用した。

  • 22番ポート: SSH接続
  • 55555番ポート: WordPress接続

image.png

インスタンス作成の確認

内容に問題なければ「起動」。

image.png

キーペアの確認

事前に作ったあったキーペアを使った。

image.png

ec2でdockerとwordpressをインストール

sshでログイン


       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-x-x-x-x ~]$

yumをアップデート

[ec2-user@ip-x-x-x-x ~]$ sudo yum update -y
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                           | 3.7 kB  00:00:00
No packages marked for update

docker準備

dockerをインストール

[ec2-user@ip-x-x-x-x ~]$ sudo amazon-linux-extras install docker -y
Installing docker
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
Cleaning repos: amzn2-core amzn2extra-docker
10 metadata files removed
4 sqlite files removed
0 metadata files removed
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                           | 3.7 kB  00:00:00
amzn2extra-docker                                                                                                                                    | 3.0 kB  00:00:00
(1/5): amzn2-core/2/x86_64/group_gz                                                                                                                  | 2.5 kB  00:00:00
(2/5): amzn2extra-docker/2/x86_64/updateinfo                                                                                                         |   76 B  00:00:00
(3/5): amzn2extra-docker/2/x86_64/primary_db                                                                                                         |  75 kB  00:00:00
(4/5): amzn2-core/2/x86_64/updateinfo                                                                                                                | 350 kB  00:00:00
(5/5): amzn2-core/2/x86_64/primary_db                                                                                                                |  50 MB  00:00:00
Resolving Dependencies
--> Running transaction check
---> Package docker.x86_64 0:19.03.13ce-1.amzn2 will be installed
--> Processing Dependency: runc >= 1.0.0 for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: containerd >= 1.3.2 for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: pigz for package: docker-19.03.13ce-1.amzn2.x86_64
--> Processing Dependency: libcgroup for package: docker-19.03.13ce-1.amzn2.x86_64
--> Running transaction check
---> Package containerd.x86_64 0:1.4.1-2.amzn2 will be installed
---> Package libcgroup.x86_64 0:0.41-21.amzn2 will be installed
---> Package pigz.x86_64 0:2.3.4-1.amzn2.0.1 will be installed
---> Package runc.x86_64 0:1.0.0-0.1.20200826.gitff819c7.amzn2 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================================================================================
 Package                           Arch                          Version                                                     Repository                                Size
============================================================================================================================================================================
Installing:
 docker                            x86_64                        19.03.13ce-1.amzn2                                          amzn2extra-docker                         37 M
Installing for dependencies:
 containerd                        x86_64                        1.4.1-2.amzn2                                               amzn2extra-docker                         24 M
 libcgroup                         x86_64                        0.41-21.amzn2                                               amzn2-core                                66 k
 pigz                              x86_64                        2.3.4-1.amzn2.0.1                                           amzn2-core                                81 k
 runc                              x86_64                        1.0.0-0.1.20200826.gitff819c7.amzn2                         amzn2extra-docker                        3.7 M

Transaction Summary
============================================================================================================================================================================
Install  1 Package (+4 Dependent packages)

Total download size: 65 M
Installed size: 270 M
Downloading packages:
(1/5): libcgroup-0.41-21.amzn2.x86_64.rpm                                                                                                            |  66 kB  00:00:00
(2/5): pigz-2.3.4-1.amzn2.0.1.x86_64.rpm                                                                                                             |  81 kB  00:00:00

(省略)

dockerサービス起動

[ec2-user@ip-x-x-x-x ~]$ sudo systemctl start docker

dockerサービス状態確認

[ec2-user@ip-x-x-x-x ~]$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-03-14 05:59:36 UTC; 25s ago
     Docs: https://docs.docker.com
  Process: 3722 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS)
  Process: 3709 ExecStartPre=/bin/mkdir -p /run/docker (code=exited, status=0/SUCCESS)
 Main PID: 3731 (dockerd)
    Tasks: 8
   Memory: 36.6M
   CGroup: /system.slice/docker.service
           mq3731 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=1024:4096

Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354394783Z" level=info msg="scheme \"unix\" not registered, f...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354629298Z" level=info msg="ccResolverWrapper: sending update...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.354858217Z" level=info msg="ClientConn switching balancer to ...le=grpc
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.394039324Z" level=info msg="Loading containers: start."
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.858998545Z" level=info msg="Default bridge (docker0) is assig...ddress"
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.915341056Z" level=info msg="Loading containers: done."
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.942822332Z" level=info msg="Docker daemon" commit=4484c46 gra...3.13-ce
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.943274164Z" level=info msg="Daemon has completed initialization"
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal systemd[1]: Started Docker Application Container Engine.
Mar 14 05:59:36 ip-172-31-20-108.us-east-2.compute.internal dockerd[3731]: time="2021-03-14T05:59:36.967245548Z" level=info msg="API listen on /var/run/docker.sock"
Hint: Some lines were ellipsized, use -l to show in full.

dockerをec2-userで実行可能にする

[ec2-user@ip-x-x-x-x ~]$ sudo usermod -a -G docker ec2-user
[ec2-user@ip-x-x-x-x ~]$ exit

※ここで一度ログアウトして再度ログインすることで権限が反映される

docker-compose

docker-composeインストール

[ec2-user@ip-x-x-x-x ~]$ sudo curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   633  100   633    0     0   9447      0 --:--:-- --:--:-- --:--:--  9447
100 11.6M  100 11.6M    0     0  22.2M      0 --:--:-- --:--:-- --:--:-- 44.5M

curlのURLは↓ここでdocker-composeのバージョンを確認して適宜書き換える。(2021/03/14時点は1.28.5)
https://docs.docker.com/compose/install/#install-compose-on-linux-systems

docker-compose実行権限設定

[ec2-user@ip-x-x-x-x ~]$ sudo chmod +x /usr/local/bin/docker-compose

バージョン確認

[ec2-user@ip-x-x-x-x ~]$ docker-compose --version
docker-compose version 1.28.5, build c4eb3a1f

wordpress

docker-compose.ymlファイルを用意

[ec2-user@ip-x-x-x-x ~]$ pwd
/home/ec2-user
[ec2-user@ip-x-x-x-x ~]$ mkdir wordpress
[ec2-user@ip-x-x-x-x ~]$ vi ~/wordpress/docker-compose.yml
docker-compose.yml
version: '3.1'

services:

  wordpress:
    container_name: wp-trial
    image: wordpress
    restart: always
    ports:
      - 55555:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    container_name: wp-db
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

wordpress用のdocker-compose.ymlファイルは↓を参考に作成。
https://hub.docker.com/_/wordpress

wordpressをビルドして起動

docker-compose.ymlが入っているディレクトリに移動してdocker-compose upする。

[ec2-user@ip-x-x-x-x ~]$ cd ~/wordpress/
[ec2-user@ip-x-x-x-x wordpress]$ pwd
/home/ec2-user/wordpress
[ec2-user@ip-x-x-x-x wordpress]$ ls -la
total 4
drwxrwxr-x 2 ec2-user ec2-user  32 Mar 14 06:12 .
drwx------ 5 ec2-user ec2-user 142 Mar 14 06:12 ..
-rw-rw-r-- 1 ec2-user ec2-user 638 Mar 14 06:12 docker-compose.yml
[ec2-user@ip-x-x-x-x wordpress]$ docker-compose up --build -d
Creating network "wordpress_default" with the default driver
Creating volume "wordpress_wordpress" with default driver
Creating volume "wordpress_db" with default driver
Pulling wordpress (wordpress:)...
latest: Pulling from library/wordpress
6f28985ad184: Pull complete
db883aae18bc: Pull complete
ffae70ea03a9: Pull complete
1e8027612378: Pull complete
3ec32e53dce5: Pull complete
3bb74037bf77: Pull complete
feda0fbd85b1: Pull complete
b2244185b327: Pull complete
8852ae668073: Pull complete
985e21deb66e: Pull complete
f262da4e7afa: Pull complete
157f3d683e13: Pull complete
990684a56233: Pull complete
c80999e49328: Pull complete
02585da80b89: Pull complete
d68ab7635a0a: Pull complete
5a577fb48682: Pull complete
d27e8a2c96b8: Pull complete
f94fa08d2764: Pull complete
6db2ebd4cef9: Pull complete
9c4c1399bbb1: Pull complete
Digest: sha256:92fb18e472ce46f289b475457289075c20387374d79bc41724689aa88112eab1
Status: Downloaded newer image for wordpress:latest
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
6f28985ad184: Already exists
e7cd18945cf6: Pull complete
ee91068b9313: Pull complete
b4efa1a4f93b: Pull complete
f220edfa5893: Pull complete
74a27d3460f8: Pull complete
2e11e23b7542: Pull complete
39ac93d44c47: Pull complete
dfd9db50d4ea: Pull complete
4e97f54f11a3: Pull complete
ebfb95795c5f: Pull complete
Digest: sha256:5f649e87093a5b6b863f5c5277b2d2aa797b04d68657494e0f28ffabfa25e781
Status: Downloaded newer image for mysql:5.7
Creating wp-trial  ... done
Creating wp-db ... done
[ec2-user@ip-172-31-20-108 wordpress]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                   NAMES
d0296aeb5318        mysql:5.7           "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp, 33060/tcp     wp-db
c01446d87997        wordpress           "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:55555->80/tcp   wp-trial

Wordpressにアクセスしてみる

image.png


参考

AWS公式 Dockerインストール手順
AWS EC2インスタンスにdockerとdocker-composeをインストールして簡単なWEBサービスを立ち上げる方法

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

【学習メモ】AWS SQS Loose Coupling

Loose Coupling

疎結合の意味。密結合の反対。
システム上で、各要素が独立し、互いの関連性が弱い。
疎結合の設計によって、一部のシステム要素は故障が起きても、影響が他の要素に及びにくいというメリットがある。また、一つの要素を修正しても、他の要素への影響を考えなくても良いメリットもある。

SQSとは

SQS(Simple Queue Service)
二つのプログラムが通信する際にポーリング処理を実施するサービス。

本来ならば、直接通信だと、受信側の処理がいっぱい溜まってて、送信側のキューがうまく届かないかもしれない。
SQSは中継所みたいな役割を果たして、送信側が送ってきたキューを溜め込んで、受信側が通信内容を問合せして、SQSの中にキューがあれば送信。

・SQSの中身は Request QueueとResponse Queueがある。
SQS.png

特徴

・フルマネージド型
・高可用性:複数のサーバー/データセンターにメッセージを保持する。
・高スケーラビリティ:多数の送信者と受信者に対応可能。
・高スループット:メッセージが増加しても高スループットを維持できる。
・低コスト:無料枠あり、従量課金。

◯メッセージサイズ:最大256KB
※Extended Client Libraryを利用すると、2GBまでのメッセージの送受信が可能。

◯保持期限:
デフォルト:4日間保持
※60秒から14日の間で変更可能。

※30日以上、キューに以下のリクエストがない場合、SQSはそのキューを削除する可能性がある。
SendMessage、ReceiveMessage、DeleteMessage、GetQueueAttributes、SetQueueAttributes。

処理形式

二つの処理形式がある。
◯標準キュー
◯FIFO(First in First out)

標準キュー

なるべく順番通りに処理するが、順番が前後する場合もある。
少なくとも 1 回のメッセージ配信をサポートする。

FIFO

順番通りに処理。最初に入ったキューを優先的に処理。

SQSの追加機能

Short Poling

キューが空の場合でも即時にリターンする。

Long Poling

キューが空の場合はタイムアウトまで待つ。

※Long polingとShort PolingはReceive Message Wait Timeで設定する。
0=Short Poling。最大は20まで。

Visibility Timeout

®️ref:https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html

コンシューマーがキューからメッセージを受信して処理しても、そのメッセージはキューに残ったままです。
Amazon SQS では、メッセージが自動的に削除されません。
Amazon SQS は分散システムであり、接続の問題やコンシューマーアプリケーションの問題などが原因で、
コンシューマーが実際にメッセージを受信するという保証がないためです。
そのため、コンシューマーはメッセージを受信して処理した後、キューからメッセージを削除する必要があります。

メッセージが受信された直後は、メッセージはキューに残ったままです。
他のコンシューマーが同じメッセージを再処理しないように、Amazon SQS は可視性タイムアウトを設定しています。
Amazon SQS では、この時間内に他のコンシューマーが同じメッセージを受信したり処理したりすることはできません。
メッセージのデフォルトの可視性タイムアウトは 30 秒です。
最小値は 0 秒、最大スケールは 12 時間です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS認定ソリューションアーキテクトアソシエイト 合格体験記

【AWS認定ソリューションアーキテクトアソシエイト 合格体験記】

image.png
3/13に合格しました。700点台でした。800点に乗せたかった。
(2/26に1度目の受験をしましたが不合格でした。。)

1度落ちてしまった反省も踏まえて、簡単にまとめてみました。
自分の備忘録のような記事になってしまっているかもしれないです。

はじめに

クラウドプラクティショナーとはレベルが違いました。
私は今回のSAAも同じような感覚で受験したため1度不合格になってしまいました。
通勤電車内でのUdemyの模擬問題のみで受験しました。

ITパスポートと同じような難易度と書かれているサイトなどがありますが、ITパスポートよりも確実に難しいです。
AWSの一つ一つの用語をきっちり理解する必要があります。

不合格後にUdemyの短期突破講座を利用して、効率的に学び直しました。

AWS認定ソリューションアーキテクトアソシエイトの概要

720/1000 点 なので、7.2割解けたら合格です。65問です。

分野 1: レジリエントアーキテクチャの設計 (試験出題比率:30%)
1.1 多層アーキテクチャソリューションの設計
1.2 可用性の高いアーキテクチャやフォールトトレラントなアーキテクチャの設計
1.3 AWS のサービスを使用したデカップリングメカニズムの設計
1.4 適切な回復力のあるストレージの選択

分野 2: 高パフォーマンスアーキテクチャの設計 (試験出題比率:28%)
2.1 ワークロードに対する伸縮自在でスケーラブルなコンピューティングソリューションの識別
2.2 ワークロードに対するパフォーマンスとスケーラブルなストレージソリューションの選択
2.3 ワークロードに対するパフォーマンスが高いネットワーキングソリューションの選択
2.4 ワークロードに対するパフォーマンスの高いデータベース ソリューションの選択

分野 3: セキュアなアプリケーションとアーキテクチャの設計 (試験出題比率:24%)
3.1 AWS リソースへのセキュアなアクセスの設計
3.2 セキュアなアプリケーション階層の設計
3.3 適切なデータセキュリティオプションの選択

分野 4: コスト最適化アーキテクチャの設計 (試験出題比率:18%)
4.1 コスト効率が高いストレージソリューションの識別
4.2 コスト効率が高いコンピューティングおよびデータベース サービスの識別
4.3 コスト最適化ネットワークアーキテクチャの設計

(AWS認定公式サイトから引用)

詳しい内容は、こちらからご確認頂くと良いと思います。
AWS認定 ソリューションアーキテクトアソシエイト

学習教材

使用した学習教材は3つです。

一夜漬け AWS認定ソリューションアーキテクト アソシエイト[C02対応]直前対策テキスト

AWS認定ソリューションアーキテクト アソシエイト試験:短期突破講座(300問の演習問題)

【SAA-C02版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)

私が不合格から反省して実践した学習手順

1、テキストを1周を読む。わかりやすく説明されています。

2、テキストに確認問題があるのでそちらも解いてみる。
(実際の試験にも似たような問題が出ました。)

3、Udemy ハンズオンなしの短期突破講座を使う。
(12時間のオンデマンドビデオですが、空き時間に2倍速で聞けばすぐです。)

4、Udemy ハンズオンなしの短期突破講座の総復習問題①を解く。
(出題の可能性の高い問題が厳選されています。)

5、Udemy ハンズオンなしの短期突破講座の総復習問題①の不正解問題の復習

6、Udemy 模擬試験を解く。
(全6つありますが、番号が若い順に優先的に勉強するといいと思います。)

7、Udemy 模擬試験の不正解問題の復習

8、試験の直前に、テキストを利用して最終仕上げ。

※Udemyの講座はセール時に購入すると出費を抑えられます。
模擬問題の正解、不正解などと絞り込みが出来るので活用するといいと思います。

今後の反省

ここまで読んでいただきまして、ありがとうございます。
私の学習手順が参考になれば幸いですが、ここは非効率ではないか。など
反面教師にでもなることができたら記事を書いて良かったと思えます。

もし良い学習方法などありました、コメントいただけますととても嬉しいです。

今回は正直なところ運が良かったから合格しただけかもしれません。
力不足であることを真摯に受け止めて、これからもAWSに触れていって、
ポートフォリオにも組み込んで、より実践的な知識を身につけていこうと思います。

通勤電車や業務の休憩時間の空き時間が長くてもったいないので、
これまでと同様にUdemyの模擬試験を解いて、AWS DVA,SOAの取得を目指します。

・おわりに Udemyの模擬試験の点数推移を参考に。

このような感じでした。
1度目は、全問回答しないで問題文と解答を照らし合わせました。

短期突破講座

総復習問題① 0%正解→32%正解→53%正解

【SAA-C02版】

基本① 0%正解→53%正解→52%正解→66%正解→83%正解→91%正解
高難易度② 0%正解→24%正解→41%正解→60%正解→72%正解→85%正解
高難易度③ 0%正解→35%正解→46%正解→50%正解→61%正解→76%正解
高難易度④ 0%正解→35%正解→40%正解→61%正解→70%正解→79%正解
高難易度⑤ 0%正解→35%正解→63%正解→76%正解→85%正解
高難易度⑤ 0%正解→43%正解→50%正解→56%正解→76%正解

※締め切りを設定していたので上記のような点数でも受験しました。
時間に余裕のある方は、これ以上を目指すと合格に近づくと思われます。

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

ポートフォリオのAWSネットワーク構成を整理した【Rails + Nginx + Unicorn + MySQL】

Railsアプリポートフォリオのネットワーク構成を頭の整理と備忘を兼ねて整理してみました。
俯瞰的に整理するものなので各リソースやネットワークの細かい設定等には触れません。
初学者なりに自分の言葉で整理したものになっていますので、おかいし点などありましたらご指摘いただけると幸いです。

はじめに:参考書籍

AWSのネットワーク構築において以下の本を参考にさせていただきました。
初学者でも大変分かりやすく、かつ内容に従って操作することで本格的でセキュアなネットワークを構築できるハンズオン形式にもなっており、これ無くしてこのネットワーク環境は作れなかったと思います。
まだ発刊されて日も浅い(2021年3月時点)ため、ネット上の記事や古い書籍でありがちな、解説のUIと現在のUIが違って迷う、ということもほぼ無かったのも良かったです。おすすめです!

ネットワーク構成図

ネットワーク構成図_公開用.png

開発環境・デプロイ

開発・テストはDockerリモートコンテナ上で実施
ソースコードはGitHubで管理、デプロイはCapistranoで行う

IAM

IAM(Identity and Access Management)はAWSへのアクセスを安全に管理するための仕組み
AWSアカウントのデフォルトユーザであるルートユーザは権限が強く、通常の開発等で使うにはリスクが大きい
そのため別途権限を抑えたユーザを作成し、こちらでネットワークの構築を行う
また仮想デバイスを用いたMFA(多要素認証)を導入し、更にセキュアな接続を実現する

VPC

ここは私の領域ですよ、と宣言する領域。Virtual Private Cloud
この中にAWSのネットワークを構築していく

アベイラビリティゾーン

耐障害性を上げるために、同じ東京リージョンの中でも異なるアベイラビリティゾーンにそれぞれサブネットを構成する

サブネット

VPCのIPアドレスの範囲を分割する範囲。分割する理由は主に以下2点

  • 役割の分離:外部に公開するリソースかどうかを区別するため
  • 機器の分離:AWS内での物理的な冗長化を行うため

今回は、2つのアベイラビリティゾーンにそれぞれパブリック・プライベートサブネットを用意する

インターネットゲートウェイ

VPCで作成したネットワークとインターネット間の通信を可能にするためのもの

NATゲートウェイ

インターネットゲートウェイを使った通信ではVPCのリソースは外部のネットワークと直接通信を行うため、VPCリソースはパブリックIPを持っている必要がある
しかしパブリックIPを持つ=インターネットに直接公開されている状態になり、サブネットをパブリックとプライベートに分けた意味が無くなる
プライベートサブネットのリソースは、インターネットに出ていく必要はあってもインターネットから直接アクセスはされたくない
→このような要求を実現するための仕組みがNAT(ネットワークアドレス変換)

AWSではこの役割を果たすのがNATゲートウェイになる

ルートテーブル

サブネット同士、あるいはサブネットと各ゲートとの間には、通信をするための経路がまだ無い
そのため、サブネットが別のサブネット内のリソースを見たり、サブネット外のリソースにアクセスができない
サブネット間の通信経路を設定するために、AWSにはルートテーブルという機能がある
今回はサブネットが4つある すべてのサブネットにルートテーブルを作成する必要がある(ただし複数のサブネットで同じルートテーブルを共有することは可能

これを踏まえ今回作成するのは以下3つ

  • パブリックルートテーブル:パブリックサブネット1,2共有
    • 外部とはIGW経由、プライベートサブネットとはLocalターゲットとしてアクセス
  • プライベートルートテーブル1:プライベートサブネット1用
  • プライベートルートテーブル2:プライベートサブネット2用
    • 外部とはNATGW経由、パブリックサブネットとはLocalターゲットとしてアクセス

セキュリティグループ

今のままだとインターネットを通じてどんなアクセスもできてしまう
VPC内のリソースを守るため、外部からのアクセス制限をつける必要がある
→セキュリティグループという機能を用いて実現する

セキュリティグループでは、外部からのアクセスを次の2つの概念で制御できる

  • ポート番号
    • サービスのアクセスで使われる80番(HTTP)、443番(HTTPS)、
    • 管理者としてサーバ接続時に使われる22番(SSHなど)
  • IPアドレス

今回は以下2つのセキュリティグループが必要

  • 全てのリソースに接続するための入り口となる「踏み台サーバ」
    • インバウンドルール:SSH接続(22番)のみ
  • リクエストや処理を分散する「ロードバランサー」
    • インバウンドルール:HTTP(80番)とHTTPS(443番)接続のみ

EC2(踏み台サーバ)

今回は機器の冗長化、負荷分散のために2つのアベイラビリティゾーンにサブネットを構築した
しかしリソースを増やすということはそれだけ侵入の経路が増えセキュリティ上のリスクが高まることになる
そこで全てのリソースに接続するための入り口となる踏み台(bastion)サーバを用意し、
このサーバ経由でないと各リソースに接続できないようにする

踏み台サーバは目的のリソースへの通り道となる以外の用途がないため、低スペックでもOK
EC2インスタンス上に構築する
踏み台サーバはパブリックサブネット1に設置し、先程作成した踏み台サーバ用のセキュリティグループを適用する(=SSH接続のみ許可する。秘密鍵はローカルマシンで保持)

EC2(Webサーバ・アプリケーションサーバ)

実際のアプリケーションを稼働させるEC2インスタンス
この中にWebサーバとしての役割を果たすNginx、Rackアプリケーション用サーバとしての役割を果たすUnicorn、アプリ本体であるRailsをインストールする
プライベートサブネット1,2にそれぞれ設置

こちらもssh認証の秘密鍵はローカルで持ち、ssh-agentを利用してセキュアに多段接続する(踏み台サーバには秘密鍵をアップしない)

ここで踏み台サーバとWebサーバの違いを整理する

項目 踏み台 Webサーバ
設置サブネット パブリック プライベート
パブリックIP 必要 不要
セキュリティグループ デフォルト+SSH接続 デフォルト+HTTP, HTTPS接続
誰がいつ接続する? 管理者が必要に応じて Webサービスのユーザが常時
接続形態 直接接続 間接接続(ロードバランサ経由)

ロードバランサー

ユーザが増えてくると1台のWebサーバではリクエストを捌き切れなくなる可能性がある
スケールアウトの方法として用いられるのがロードバランサーで、リクエストの分散を実現できる

設定項目は以下

  • アベイラビリティゾーン:インターネットゲートウェイへの経路があるサブネット(今回はパブリックサブネット1,2)があるゾーンを指定する必要がある
  • セキュリティグループ:デフォルト+HTTP, HTTPS接続(内部向けと外部向け)
  • ターゲットグループ:Webサーバ1, 2、プロトコルはHTTP

データベース

AWSのサービスの一つであるRDSを用いて実現
RDSは以下の4点で構成される

  • データベースエンジン
    → 今回はMySQLを使用)

  • パラメータグループ
    → 主にデータベースエンジン固有の設定を行うためのもの

  • オプショングループ
    → 主にRDS固有の設定を行うためのもの

  • サブネットグループ
    → データベースサーバーを複数のアベイラビリティーゾーンに分散させて配置させる時に使われる設定
     RDSはサブネットグループを指定することで自動的に複数のアベイラビリティーゾーンにデータベースを作成し、
     耐障害性を上げてくれる(マルチAZ)

S3

今回のアプリではユーザのアイコンや投稿機能で画像をアップロードすることが可能
その画像を保管する場所として、AWSのストレージサービスであるS3を利用する
S3はVPCの外に配置するものである
そのためIAMでS3にアクセスするためのロールを作成し、EC2に適用することでアプリからS3へのアクセスを実現する

Amazon Route 53

Webアプリを公開するためには分かりやすくて覚えやすいドメインが必要
AWSではドメインを取得することができるRoute 53というサービスがある
こちらで希望のドメインを取得することで公開用のURLを発行することができる
(費用は$10~15/年 前後)

また、Route 53では他に以下の設定を行う

  • SSLサーバー証明書の発行(Certificate Manager)
    → ドメインの正しさを保証する証明書の発行を行う。
     今回はドメイン検証済み(DV)証明書を使う
     発行した証明書を使い、HTTPSのリスナーをロードバランサーに追加する

  • パブリックDNSの設定
    → 踏み台サーバとロードバランサーに対し設定する

  • プライベートDNSの設定
    → 踏み台サーバ、Webサーバ1,2、DBサーバに対し設定する

Amazon SES

SES(Simple Email Service)はメールの送受信行う機能を提供するAWSのサービス
今回のアプリではパスワードを忘れた際に再発行するためにメールの送信を行うため用意する
受信に関する設定は不要のため行わない

通常のメール送受信においては、以下プロトコルが利用される

  • SMTP(Simple Mail Transfer Protocol)
    → 送信用のプロトコル

  • POP3(Post Office Protocol Version 3)
    → 受信用のプロトコル。メールは受信者のローカルに取り込まれる

  • IMAP4(Internet Message Access Protocol Version 4)
    → 受信用のプロトコル。メールはサーバ上に保管されるためインターネットアクセスが必要

アプリからメールを送るには、メール送信用のIAMユーザを作成し、Amazon SES API または Amazon SES SMTPインターフェイスで認証する必要がある
今回は通常のメールサーバと同じ方法で実装ができるAmazon SES SMTPインターフェイスを利用している

CloudWatch

AWSで提供されている、各種サーバ等の運用状況を監視するツール
今回はWebサーバ・アプリサーバを包含するEC2インスタンスの死活監視、CPU使用率監視を行っている

おわりに

CircleCI/CDを使った自動テスト・自動デプロイも導入したいと考えています。
導入できたらこちらも更新しようと思います。

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

AWS SDK for Javaで "JAXB is unavailable" と言われたら

環境

  • AWS SDK for Java S3 1.11.327
  • Zulu 11.0.9.1
  • M1版 macOS Big Sur 11.2.3

現象

AmazonS3#putObject() でS3にファイルをアップロードすると、 JAXB is unavailable. Will fallback to SDK implementation which may be less performant という警告ログが出ます。

原因

JDK 11にはJAXB実装が含まれないからです。JDK 8とかだと、この現象は起こらないはずです。

解決方法

JAXBを明示的に追加すればOKです。

pom.xml
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.11.327</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
        </dependency>
        <!-- この2つ! -->
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.3</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

注意点

jakarta.xml.bind-apiやjaxb-implのバージョンは必ず 2.3.3 にしてください。最新の 3.0.0 だと問題は解決しません。

おそらくAWS SDK側が jakarta.* ネームスペースに対応していないからだと思われます。

参考URL

Issue立てました

「いいね」などよろしくお願いします。

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

Cronが動かないとは何ごとぞ!

背景

先日、Amazon EC2 (Amazon Linux 2) インスタンス(※1)起動時に自動でスクリプトを実行したく、cron を設定していたのですが、スクリプトが実行されない現象にぶつかりました。その際の対処をここにメモとして残しておきます。

(※1)インスタンスタイプ:g4dn.xlarge ...機械学習などに用いるGPUを利用できるインスタンスタイプです

 

先人たちの知恵をお借りするなどして解決できたことを、この場をお借りして感謝するとともに、大変恐縮ですが自分のメモとして、こちらへまとめておきます。

環境

(本番)
- AWS EC2 (Amazon Linux 2)
- Python 3.7.9    ※2021/02/21時点のAmazon Linux2でのデフォルト
- Putty 0.74- AWS EC2 (Amazon Linux 2)

0. Cron とは

IT用語辞典 e-Words ―cronの項目 より引用:

cronとは、多くのUNIX系OSで標準的に利用される常駐プログラム(デーモン)の一種で、利用者の設定したスケジュールに従って指定されたプログラムを定期的に起動してくれるもの。
利用者はcrontab(“cron table”の略)コマンドで実行したいプログラムやコマンド、シェルスクリプトなどと実行日時を指定すると、同名のテキストファイル(crontabファイル)に設定が保存される。システムに常駐するデーモンの一つであるcrond(“cron daemon”の略)がcrontabファイルに書かれたスケジュールに従って、決まった日時に指定されたプログラムを実行する。

1. 現象

cron で設定したスクリプトが、EC2 インスタンス起動時に起動しない。

2. 原因

まず考えられたのは、cron コマンドの設定ミス。
コマンドは、単純にリブート時にとあるスクリプトを実行するというもの。
しかしながら、何度見直してもコマンドの記述に間違いは見当たりません。

crontab
@reboot /home/(username)/app/on_start.sh

 

実行されるスクリプトの内容も、1つのpythonファイルを実行するだけの簡単なものです。
こちらもディレクトリの設定や処理内容に間違いはありませんでした。

on_start.sh
#!/bin/bash

cd `dirname $0`

SHELL=/bin/bash
# パスを通す
source /home/(username)/.bashrc
# 好きなPython環境を設定
source activate tensorflow2_latest_serving
# 起動から45分後にインスタンスを停止する
sudo shutdown +45

cd /home/(username)/app
python3 app.py

3. 対処

いろいろと調べていた中で末尾に記載した参考記事(特に @nagimaruxxx 様の記事)を見付け、それに沿って以下を実行しました。

a) crond ステータス確認

service crond status    # ステータス確認
service crond start     # 起動

⇒ crond は稼働していました。

 

※以降 b)~d) はルート権限で実行が必要。「sudo su -」コマンドでルート権限に移行する。
 ユーザー権限に戻る際は、「exit」を入力して[enter]キー押下。

b) run level 確認

chkconfig --list crond
crond           0:off   1:off   2:on    3:on    4:on    5:on    6:off

 
⇒ 私の環境では、何故か上記コマンドを受け付けずに確認ができなかったため、確認なしで、直接に下記コマンドを実行しました。

chkconfig --level 2345 crond on    # run level 2,3,4,5 を on とする

c) ファイルの実行権限

ls -l on_start.sh
-rw-r--r-- 1 dev dev 1  2月 25 10:51 2021 on_start.sh

chmod +x on_start.sh    # 権限を付与

ls -l on_start.sh
-rwxr-xr-x 1 dev dev 1  2月 25 10:52 2021 on_start.sh

⇒ 変更前は「644」でしたが、これに +x して「755」へ。
 一番の原因は、この実行権限だった模様。

d) noanacron の導入

デフォルトで anacron という cron がインストールされていましたが、使いづらい点があり、私も @nagimaruxxx 様の記事を参考に noanacron をインストールしました。

yum -y install cronie-noanacron
yum remove cronie-anacron

4. 結果

cronにより、EC2 インスタンス起動時(再起動時)に指定したスクリプト(上述の on_start.sh)が起動されるようになりました!
また、インスタンス起動から指定通り45分後にインスタンスが停止されることも確認できました。

めでたしめでたし。


参考


(編集後記)

まさか cron が動かないとは思わず(まさしくタイトル通り「Cronが動かないとは何ごとぞ!」と思っていたため)、参考記事に辿り着くまで、さらには、ファイルの実行権限が原因であることを突き止めるまでに、多くの時間を費やしてしまいました。

私自身は通常、スクリプトファイルやPythonファイルを、Puttyからのコマンドや、WinSCPでローカルからEC2へコピーするなどしているのですが、その際に実行権限を確認することをあまり行なっておりませんでした。
今後は、追加・変更したファイルやディレクトリの実行権限を必ず確認するよう、手元のチェック項目へ追記しました。

皆様はこれを反面教師として考えていただけたら幸いです。

最後までお付き合いいただきまして、ありがとうございました。

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

【AWS】Lambdaの初回起動を早くする方法

はじめに

個人請負でとあるサイトをサーバーレスで構築しています。
よくあるAmazon API Gatewayでリクエストを受けてAWS Lambdaで処理を行って結果を返却する簡単な構造なのですが・・・Lambdaからのリクエストが初回だけとにかく遅い!
原因は関数実行前の準備に時間がかかるからです。
Lambdaというサービスの内部では、初回実行時に実行する関数のパッケージのロードや展開、ランタイムの起動など色々な「実行前準備」をやってくれています。
しかし、一定時間関数が呼び出されないと「実行前準備」がアンロードされてしまうのです。そのため実行頻度の低いLambdaの場合は毎回遅い初回実行になってしまいます。

今回は色々と試してみてなんとか解決した方法を載せておきます。

前提

この方法が有効なケースは以下の場合です。

  • Lambdaの実行頻度が低い。※30分に1度程度しか実行されないなど

デプロイパッケージのサイズをランタイムに必要な最小限のサイズにしていることやJavaよりPythonを選択している等も速度に影響してきますが今回は実行頻度が低い場合のみを対象としています。

解決した方法

なんと「Lambdaを定期的に実行する」だけです。
邪道のような気もしますがこれが一番効果がありました。
一定時間関数が呼び出されないと「実行前準備」がアンロードされてしまうのでアンロードされる前に再度実行だけしておく。そりゃ効果ありますよね。

今回は5分毎に実行するようにLambdaのトリガーを追加しました。
始めは1分毎で設定してみたのですが5分でも大丈夫そうなので5分毎に変更しています。
AWS Lambdaは関数に対するリクエストの数とコードの実行時間に基づいて課金されるので「コストあがっちゃうよ!」ってお考えの方もいらっしゃるかもしれませんが安心してください。1日288回プラスになるだけです。誤差です。

では早速設定してみます。

関数を選択した状態で「トリガーを追加」をクリックします。
1.png

トリガーの種類を選択できるので、「EventBridge(CloudWatch Events)」を選択します。
2.png

「新規ルールの作成」を選択し、ルール名を入力します。
今回は「scheduled-execution」としました。
3.png

次にルールタイプを選択します。「スケジュール式」を選択します。
スケジュール式に「rate(5 minutes)」と入力します。
※5分毎に実行しますよの設定です。
4.png

あとは追加ボタンを押下すれば完了です。
定期実行されてもエラーにならないよう関数側を必要に応じて修正してください。パラメータなしで実行されるのでそれを判定に使っても良いかと思います。

これできっと何かが数秒早くなって世界のどこかで誰かが喜びます。

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

【学習メモ】AWS CloudFront

CloudFront:
AWSが提供するCDN(Content Delivery Network)サービス

CDSとは

CDNはWEBコンテンツ配信処理を高速化するためのサービス。
スタバを例としてあげると、
例)A市にスタバのお店が設置されていない、A市市民がスタバコーヒーを飲みたい時、
スタバのあるB市に行かないと飲めない。B市への往復時間は3時間。
もしA市にスタバを設置したら、そのB市への往復時間を省ける。

CDNの話に戻ると、CDNはbranch officeみたいな役割で、CDNがあれば、
ユーザーがウェブにアクセスする際に、距離が一番近いノードにアクセスして、そのアクセスの時間を短縮可能。

CDSのメリット:
・異なる地域にアクセスする際、遅延時間が短くなる。
・requestはCDNノードで完了。オリジンサーバーの負荷軽減。

CloudFront

大規模なアクセスも世界中にあるエッジのキャパシティを活用して効率的かつ高速にコンテツ配信が可能なサービス。

・210以上のエッジロケーションがあるため、多数のエッジサーバーが用意される。それによって、全世界に高性能、高いパフォーマンスで配信可能。
・AWSWAF/AWS Certificate Managerとの連携やDDoS対策によるセキュリティ機能。
・オリジンに対してHeader/Cookie/QueryStringsによるフォワード指定で、静的なページだけでなく、動的なページ配信が可能。

現在のアーキテクチャはリージョナルエッジキャッシュが追加されより効率的な配信処理が可能になった。

Distribution設定

CloudFrontの設定において、まずはDistribution設定。

・各配信先となるドメインに割り当てるCloudFrontを設定する。
・WEB DistributionとRTMP Distribution、この二つの形式を選べる。
・使用量が最大40Gbps/10万RPS超は上限緩和申請を実施する。
・独自ドメインを指定可能。

各種設定:
・コンテンツオリジン設定:CloudFrontの配信ファイルの取得先の設定
・アクセス設定:ファイルアクセスの許可設定
・セキュリティ設定:アクセスにHTTPSを利用するかどうかの設定
・Cookie またはクエリ文字列転送の設定:オリジンへのCookie/クエリ文字列転送の要否を設定
・地域制限:特定の国のユーザーからのアクセス拒否設定
・アクセスログ設定:アクセスログを作成するかどうかの設定

※通常はWEBディストリビューションを利用するが、
Adobeメディアの場合:RTMPディストリビューションを利用する。

セキュリティー機能

CloudFrontは様々なセキュリティー設定によって、セキュアなコンテンツ配信が可能になる。

・SSL証明書の設定:コンテンツ配信のHTTPSに対応してり、配信時の暗号化通信が可能。

・オリジンカスタムヘッダーによる通信制御。
(リクエストユーザーを識別可能。)

・AWS WAFによるファイアーウォールと連携し、
 ディストリビューションに対するウェブリクエストを許可、ブロックが可能。

・AWS ShieldによるDDoS対応。
 CLoudFrontを利用すると、DDoS対応のスタンダード版が自動で設定される。

・署名付きURL/Cookieによる有効期間指定。

・GEOリストリクションによる地域情報でアクセス判定。

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

リザーブドインスタンスの種類について(スタンダード・コンバーティブル)

勉強前イメージ

リザーブドインスタンスはわかるけど、
種類があるのん・・?

調査

まずリザーブドインスタンスとは?

リザーブドインスタンスとは
1年・3年単位で購入するインスタンスのことで、時間単価の割引が行われます。
ものによっては前払いが必要なこともあります。

詳細は こちら をご確認ください。

種類について

種類がどこで見れるかというと、
リザーブドインスタンスの購入の画面で以下のようにどのようなインスタンスを購入するか選ぶ画面で確認することが出来ます。

提供クラスEC2 Management Console - Google Chrome 2021-03-13.png

画像にある通り、種類には スタンダードコンバーティブル の2種類あります。

種類を詳細に

スタンダード

  • 平均割引率 : 1年(40%) 3年(60%)
  • 交換 : 交換は出来ません
  • マーケットプレイスでの販売・購入 : 販売・購入できます

コンバーティブル

  • 平均割引率 : 1年(31%) 3年(54%)
  • 交換 : 期間内であれば、インスタンスファミリー・インスタンスタイプ・プラットフォームなど違う属性の別のリザーブドインスタンスに交換することができます
  • マーケットプレイスでの販売 : 販売・購入できません

まとめ

スタンダードコンバーティブル の違いとしては、

  • スタンダード : 割引率は高いが、インスタンスタイプなど違ったときに交換してもらえない。ただし、マーケットプレイスで販売・購入出来る
  • コンバーティブル : 割引率は低いが、インスタンスタイプなど違ったときに交換してもらえる。

必ずこのインスタンスタイプ・インスタンスファミリー使うんだ!ってときはスタンダード使えばより安くなるが、
もしかしたら変更あり得るかも・・・ってときはコンバーティブルを使えば万が一のときは交換してもらえる。
スタンダードは、マーケットプレイスで販売・購入出来るらしいけど、めんどくさそうな気がする。

勉強後イメージ

リザーブドインスタンスにもいろいろ種類があるのね・・・・
選択肢がどんどん広がって便利になる分、選ぶの大変そうだなぁ

参考

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