20200921のAWSに関する記事は21件です。

Ruby×Sinatraで作ったSlackBotをAWS Lambdaで動かしてみる

本記事で目差す構成

Untitled Diagram(1).png

① Slackで特定のアクションを実行する。(※今回はスラッシュコマンド)
② API Gatewayを介してLambdaを起動。
③ Lambdaに配置した関数を実行し情報を返す。

slackbot(2).gif

↑動作イメージとしてはこんな感じ。
今回はとある地域の現在気温を返してくれるSlackBotを動かしてみる。

対象読者

  • 簡単なSlackBotを作ってみたい人
  • AWSのLambdaに触れてみたい人

Lambdaとは?

AWS Lambda はサーバーをプロビジョニングしたり管理する必要なくコードを実行できるコンピューティングサービスです。 AWS Lambda は必要時にのみてコードを実行し、1 日あたり数個のリクエストから 1 秒あたり数千のリクエストまで自動的にスケーリングします。使用したコンピューティング時間に対してのみお支払いいただきます- コードが実行中でなければ料金はかかりません。AWS Lambda では、管理を全く必要とせずに、任意のアプリケーションやバックエンドサービスで仮想的にコードを実行できます。AWS Lambda は、高度な可用性のコンピューティングインフラストラクチャでコードを実行し、サーバーとオペレーティングシステム、システムのメンテナンス、容量のプロビショニングと自動スケーリング、コードのモニタリングやログ記録など、コンピューティングリソースのすべての管理を実行します。必要な操作は、AWS Lambda がサポートするいずれかの言語でコードを指定するだけです。(引用: Amazon公式ドキュメント)

これだけだとイマイチわかりにくいが、要するに「プログラムを実行するためのサーバーがいらない」という事。

通常、何かしらのプログラムを実行しようと思った場合、サーバーを購入したり、各種ミドルウェアをインストールしたりと色々手間がかかるものだが、Lambdaにおいてそういったものは全てAWSが管理してくれるため、開発者はソースコードの作成にだけ力を注げば良くなるらしい。

Lambdaを使うメリット

  • サーバーや各種ミドルウェアの管理が不要
    • 上述の理由から。
  • コスト削減
    • リクエスト数やプログラムの実行時間によって課金される仕組みとなっており、待機時間には課金されないため、使用する局面によっては大幅なコストダウンが可能。(常に課金され続けるEC2とは対照的)
  • オートスケーリング
    • アクセス数や負荷に応じて自動的にサーバーの数を増減してくれる。

今回のようなSlackBotの場合、常に稼働させたいというよりは必要な時のみ動いてくれれば構わないため、Lambdaを利用するにはちょうど良いと思った。

SlackBotくらい軽い実装であればHerokuなどを使ったデプロイ方法も定番だが、今時のAWSを使ってみたい感がある。

仕様

言語: Ruby2.5
フレームワーク: Sinatra
インフラ: AWS Lambda

完成形: slack-bot-on-aws-lambda

SlackBotを作成

まず、肝心のSlackBotを作成していく。

ディレクトリを作成

$ mkdir slack-bot-on-aws-lambda
$ cd slack-bot-on-aws-lambda

Rubyのバージョンを指定

# 2.5系なら何でもOK
$ rbenv local 2.5.1 

Sinatraをインストール

$ bundle init

↑のコマンドでGemfileを生成し、以下のように編集する。

./Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'sinatra'

その後、Gemをインストール。

$ bundle install --path vendor/bundle

動作確認のため、とりあえず「Hello World!」と返すページを実装してみる。

$ touch main.rb
./main.rb
require 'sinatra'

get '/' do
  'Hello World!'
end

その後、Sinatraを起動。

$ bundle exec ruby main.rb

[2020-09-21 20:47:35] INFO  WEBrick 1.4.2
[2020-09-21 20:47:35] INFO  ruby 2.5.1 (2018-03-29) [x86_64-darwin19]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2020-09-21 20:47:35] INFO  WEBrick::HTTPServer#start: pid=50418 port=4567

Sinatraはデフォルトだとポート番号「4567」で動くため、「localhost:4567」にアクセス。

スクリーンショット 2020-09-21 20.50.33.png

「Hello World!」と表示されれば成功。

天気情報を返すプログラムを実装

今回作るSlackBotの主な機能である天気情報を返すプログラムを実装していく。

OpenWeatherのAPIキーを取得

https://openweathermap.org/
スクリーンショット 2020-09-21 20.54.33.png

上記サイトに会員登録し、APIキーを取得。

スクリーンショット 2020-09-21 20.59.28_censored.jpg

英語で書かれたサービスだが、ある程度は直感的に操作できるので詳しい説明は省略。どうしてもわからなかったらググればいくらでも記事が出てくるはず。

各種Gemをインストール

この先の処理を行う上で必要なGemがいくつかあるため、このタイミングで一気にインストールしておく。

./Gemfile
gem 'faraday'
gem 'rack'
gem 'rack-contrib'
gem 'rubysl-base64'
gem 'slack-ruby-bot'

「bundle install」も忘れずに。

$ bundle install --path vendor/bundle

src/weather.rbを作成

$ mkdir src
$ touch src/weather.rb

src/weather.rbを作成し、次のように記述。

./src/weather.rb
require 'json'

class Weather
  def current_temp(locate)
    end_point_url = 'http://api.openweathermap.org/data/2.5/weather'
    api_key = # 先ほど取得したOpenWeatherのAPIキー

    res = Faraday.get(end_point_url + "?q=#{locate},jp&APPID=#{api_key}")
    res_body = JSON.parse(res.body)

    temp = res_body['main']['temp']
    celsius = temp - 273.15
    celsius_round = celsius.round

    return "現在の練馬の気温は#{celsius_round.to_s}℃です。"
  end
end

main.rbを編集

./main.rb
require 'slack-ruby-client'
require 'sinatra'
require './src/weather'

Slack.configure do |conf|
  conf.token = # SlackBotのトークン
end

get '/' do
  'This is SlackBot on AWS Lambda'
end

post '/webhook' do
  client = Slack::Web::Client.new

  channel_id = params['channel_id']
  command = params['command']

  case command
    when '/nerima'
    # スラッシュコマンド「/nerima」が実行された場合に以下の処理が走る。
      weather = Weather.new
      client.chat_postMessage channel: channel_id, text: weather.current_temp('Nerima'), as_user: true
      # 'Nerima'の部分は各自変更してOK。「Shinjuku」に変えれば新宿の気温を返すはず。
  end

  return
end

SlackBotのトークンを取得する方法については次の記事を参照。

参照: ワークスペースで利用するボットの作成
参照: API トークンの生成と再生成

実際に動作確認

SlackBotをスラッシュコマンドで呼び出すためにいくつか設定しなければならない事がある。

https://api.slack.com/apps/
スクリーンショット 2020-09-21 21.33.08.png
↑のURLにアクセスし、該当のBotを選択。

スクリーンショット 2020-09-21 21.37.35.png
左サイドメニューに「Slash Commands」という項目があるので選択し、「Create New Command」をクリック。
スクリーンショット 2020-09-21 21.40.08.png
各項目を入力していく。

  • Command: 任意のスラッシュコマンド。
    • 今回は東京度練馬区の現在気温を返す事を想定しているので「/nerima」としているが、たとえば新宿区であれば「/shinjuku」とかでもOK。)
  • Request URL: スラッシュコマンドを実行した際にリクエストしたいURL。
    • 「localhost」では動かないため、今回はngrokを使って独自のドメインを割り当てている。
    • 参照: ngrokの利用方法
    • 「bundle exec ruby main.rb」でSinatraを起動した後、別のターミナルで「ngrok http 4567」と叩いて表示されたURLを使用する。
    • postメソッドでリクエストしたいので、「https://********.ngrok.io/webhook」と入力する。
  • Short Description: スラッシュコマンドの簡単な説明。

入力が完了したら右下の「Save」をクリック。

スクリーンショット 2020-09-21 21.53.38.png
スラッシュコマンドの作成が終わったら、SlackBotを追加したチャンネルで「/nerima」と打ち込んでみる。上手くいけば画像のようにSlackBotから返答が来る。(設定で画像や名前を変えたりする事も可能。)
スクリーンショット 2020-09-21 22.04.20.png
何か不具合があった場合はターミナルにログが出力されているはずなので、適宜デバッグ。

AWS Lambdaにデプロイ

正常に動作確認できたら、いよいよAWS Lambdaで本番稼働させる。

AWS CLIをインストール

今回は「AWS CLI」と呼ばれるツールを使いながらデプロイしていくので、まだインストールできてないない場合はインストールしておく。

$ brew install awscli

IAMユーザーを作成

デプロイ作業を行うためのIAMユーザーを作成していく。
スクリーンショット 2020-09-21 22.11.08.png
まずは「IAM」→「ポリシー」→「ポリシーの作成」へと進み、JSONタブから以下の文を貼り付ける。
スクリーンショット 2020-09-21 22.13.26.png

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "apigateway:*",
                "cloudformation:*",
                "dynamodb:*",
                "events:*",
                "iam:*",
                "lambda:*",
                "logs:*",
                "route53:*",
                "s3:*"
             ],
            "Resource": [
                "*"
            ]
        }
    ]
}

参照: Minimal Deploy IAM Policy

スクリーンショット 2020-09-21 22.15.47.png
適当にポリシー名や説明を記述し、「ポリシーの作成」をクリック。

スクリーンショット 2020-09-21 22.17.27.png
次に「IAM」→「ユーザー」→「ユーザーの作成」へと進み、適当な名前を付けた後「プログラムによるアクセス」にチェックを入れて次へ進む。

スクリーンショット 2020-09-21 22.17.47.png
「既存のポリシーを直接アタッチ」から先ほど作成した「MinimalDeployIAMPolicy」を選択し、次へ進む。

スクリーンショット 2020-09-21 22.17.59.png

(タグは任意でOK)最後に確認画面が表示されるので、問題無ければ「ユーザーの作成」をクリック。

スクリーンショット 2020-09-21 22.18.13_censored.jpg
すると「アクセスキーID」と「シークレットアクセスキー」の2つが発行されるので、csvファイルをダウンロードするなりメモするなり大事に保管しておく。

AWS CLIの設定

$ aws configure

AWS Access Key ID # 先ほど作成したアクセスキーID
AWS Secret Access Key # 先ほど作成したシークレットアクセスキー
Default region name # ap-northeast-1 
Default output format # json 

ターミナルで「aws configure」と打ち込むと対話形式で色々聞かれるので、それぞれ必要な情報を入力していく。

各種ファイルを作成

AWS CLIの設定が終わったら、デプロイに必要な各種ファイルの作成を行う。

  • config.ru
  • lambda.rb
  • template.yaml
./config.ru
require 'rack'
require 'rack/contrib'
require_relative './main'

set :root, File.dirname(__FILE__)

run Sinatra::Application
./lambda.rb
require 'json'
require 'rack'
require 'base64'

$app ||= Rack::Builder.parse_file("#{__dir__}/config.ru").first
ENV['RACK_ENV'] ||= 'production'

def handler(event:, context:)
  body = if event['isBase64Encoded']
    Base64.decode64 event['body']
  else
    event['body']
  end || ''

  headers = event.fetch 'headers', {}

  env = {
    'REQUEST_METHOD' => event.fetch('httpMethod'),
    'SCRIPT_NAME' => '',
    'PATH_INFO' => event.fetch('path', ''),
    'QUERY_STRING' => Rack::Utils.build_query(event['queryStringParameters'] || {}),
    'SERVER_NAME' => headers.fetch('Host', 'localhost'),
    'SERVER_PORT' => headers.fetch('X-Forwarded-Port', 443).to_s,

    'rack.version' => Rack::VERSION,
    'rack.url_scheme' => headers.fetch('CloudFront-Forwarded-Proto') { headers.fetch('X-Forwarded-Proto', 'https') },
    'rack.input' => StringIO.new(body),
    'rack.errors' => $stderr,
  }

  headers.each_pair do |key, value|
    name = key.upcase.gsub '-', '_'
    header = case name
      when 'CONTENT_TYPE', 'CONTENT_LENGTH'
        name
      else
        "HTTP_#{name}"
    end
    env[header] = value.to_s
  end

  begin
    status, headers, body = $app.call env

    body_content = ""
    body.each do |item|
      body_content += item.to_s
    end

    response = {
      'statusCode' => status,
      'headers' => headers,
      'body' => body_content
    }
    if event['requestContext'].has_key?('elb')
      response['isBase64Encoded'] = false
    end
  rescue Exception => exception
    response = {
      'statusCode' => 500,
      'body' => exception.message
    }
  end

  response
end
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  SinatraFunction:
    Type: 'AWS::Serverless::Function'
    Properties:
      FunctionName: SlackBot
      Handler: lambda.handler
      Runtime: ruby2.5
      CodeUri: './'
      MemorySize: 512
      Timeout: 30
      Events:
        SinatraApi:
            Type: Api
            Properties:
                Path: /
                Method: ANY
                RestApiId: !Ref SinatraAPI
  SinatraAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: SlackBotAPI
      StageName: Prod
      DefinitionBody:
        swagger: '2.0'
        basePath: '/Prod'
        info:
          title: !Ref AWS::StackName
        paths:
          /{proxy+}:
            x-amazon-apigateway-any-method:
              responses: {}
              x-amazon-apigateway-integration:
                uri:
                  !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SinatraFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                httpMethod: POST
                type: 'aws_proxy'
          /:
            get:
              responses: {}
              x-amazon-apigateway-integration:
                uri:
                  !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SinatraFunction.Arn}/invocations'
                passthroughBehavior: 'when_no_match'
                httpMethod: POST
                type: 'aws_proxy'
  ConfigLambdaPermission:
    Type: 'AWS::Lambda::Permission'
    DependsOn:
    - SinatraFunction
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref SinatraFunction
      Principal: apigateway.amazonaws.com
Outputs:
  SinatraAppUrl:
    Description: App endpoint URL
    Value: !Sub "https://${SinatraAPI}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

それぞれが何を表しているのかについてここでは割愛。(この辺はawsの公式ドキュメントをほぼそのまま使っているため)
参照: aws-samples/serverless-sinatra-sample

S3バケットの作成

事前にAWS S3バケットを準備しておく必要があるため、適当にS3バケットを作成。

参照: AWS S3のバケットの作り方

デプロイ

次のコマンドを実行。

$ aws cloudformation package \
     --template-file template.yaml \
     --output-template-file serverless-output.yaml \
     --s3-bucket # 先ほど作成したS3バケット名

Uploading to a3a55f6abf5f21a2e1161442e53b27a8  12970487 / 12970487.0  (100.00%)
Successfully packaged artifacts and wrote output template to file serverless-output.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/ユーザー名/ディレクトリ名/serverless-output.yaml --stack-name <YOUR STACK NAME>

すると、ディレクトリ内に「serverless-output.yaml」というファイルが自動生成されているはずなので、こちらを元に次のコマンドを実行。

$ aws cloudformation deploy --template-file serverless-output.yaml \
     --stack-name slack-bot \
     --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - slack-bot

「Successfully」と表示されればデプロイ成功。

スクリーンショット 2020-09-21 22.49.32_censored.jpg

「Lambda」→「関数」と進むと先ほどデプロイした内容が表示されるので、「API Gateway」内に記載されているAPIエンドポイントにアクセス。

スクリーンショット 2020-09-21 22.53.09.png

./main.rb
get '/' do
  'This is SlackBot on AWS Lambda'
end

main.rb内のget '/' リクエストの期待通り「This is SlackBot on AWS Lambda」が返ってくれば正常に動作していると判断してOK。

Slash CommandsのRequest URLを変更

https://api.slack.com/apps/

スクリーンショット 2020-09-21 23.00.35_censored.jpg
再びSlackBotの設定ページにアクセスし、「Slash Commands」からRequest URLを先ほど作成されたエンドポイントに変更する。(https://********.execute-api.ap-northeast-1.amazonaws.com/Prod/webhook」)

スクリーンショット 2020-09-21 23.06.41.png
最後にもう一度、Slackチャンネルで「/nerima」と打ち込み、ちゃんとレスポンスが返ってくればめでたしめでたし。

スクリーンショット 2020-09-21 23.07.45.png
もし上手く行かなかった場合はCloudWatchにログが出力されているはずなので、適宜デバッグ。

あとがき

お疲れ様でした!

今回、簡単なSlackBotをLambdaで動かすというテーマでAWS Lambdaに触れてみました。
大した機能は実装できていないのでLamdaの素晴らしさを全て実感というわけにはいきませんでしたが、上手く使えばかなり便利なサービスだと素人ながら感じています。

難しい操作はしていないため、基本的には手順通りに進めていけば動くはずですが、もし詰まるところがあった場合はコメント欄などで指摘していただけると嬉しいです。

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

SAA試験ELBの基本機能

SAA-C02の模試を受け55%ほどしか点数が取れませんでした。
勉強に使用していた資料はSAA-C01用のものだったので、不足分を補うためにBlack Beltを試聴しました。

参考にした資料

AWS Black Beltの資料をまとめました
YouTubeでBlack Beltをみると資料の一部訂正なども入っていたので、pdfのみではなく動画で一度視聴していただいた方がいいかもしれません
https://d1.awsstatic.com/webinars/jp/pdf/services/20191029_AWS-Blackbelt_ELB.pdf

ELB概要

ELBを導入するメリット 

負荷分散してスケーラブルなシステム構築が可能
ELB自体も負荷の増減に応じて自動的にスケールする

ELBの種類

Aoolication Load Balancer(ALB)
Network Load Balancer(NLB)
Classic Load Balancer(CLB)
※この記事ではカッコ内の略称で記述します

ELBの基本性能

ELBの種類によって使用できない設定もあるので注意
使用できるELBの種類をタイトルの横に記述してあります

複数のアバイラビリティーゾーン(AZ)に分散【ALB/NLB/CLB】

ELBは正確には二段階で負荷分散を行っています
1.DNSラウンドロビンで各AZ内のELBに振り分ける
2.負荷が均等になるようにEC2に振り分けを行う(ELBの種類により振り分けを行うアルゴリズむが異なる)

アルゴリズムの種類

ALB NLB CLB
ラウンドロビンルーティング フローハッシュアルゴリズム TCP:ラウンドロビン,HTTP/HTTPS:hte least outstanding requests routing algorithm

クロスゾーン負荷分散【ALB/NLB/CLB】

リージョン内の複数AZ内に負荷分散可能
複数リージョンへの分散にはRoute53を併用
AZごとのEC2インスタンス数が異なってもクロスゾーン負荷分散によりすべての負荷分散を均等にできる

クロスゾーン負荷分散のデフォルト設定

ALB NLB CLB
デフォルトで有効 デフォルトで無効 デフォルトで有効

同一インスタンスへの複数ポートに負荷分散【ALB/NLB】

・EC2インスタンスをターゲットに割り当てる際に複数のポートを個別のターゲットとして登録することが可能
・一つのインスタンスに対して複数ポートで負荷分散することが可能
・コンテナ用いる際には、同一インスタンス上で複数のコンテナをを運用する必要があるので有効
・ECSを利用の場合には動的ポートマッピング機能を利用できる

IPアドレスをターゲットに指定【ALB/NLB】

オンプレミス のサーバーにも負荷分散を行う場合、Direct Connectなどでオンプレサーバーと接続を行いIP指定で負荷を分散することが可能。

ELBのモニタリング・ログ

ヘルスチェック【ALB/NLB/CLB】

ELBは正常なターゲットのみにトラフィックをルーティングする
ELBは設定値に基づき、ターゲットに対してヘルスチェックを定期的に行い正常なターゲットかを判定する
正常判定が厳しすぎるとインスタンスが使えるまでに時間がかかり、逆に異常との判定が厳しすぎても、過負荷時に処理できるインスタンスを減らしてしまうことにもなる

設定 説明 設定例
プロトコル(HealthCheckProtocol) ターゲットにヘルステェックをジックするときにロードバランサーが使用するプロトコル HTTPで200が返るのを確認
ポート(HealthCheckPort) ターゲットでヘルスチェックを実行するときにロードバランサーが実行するポート。デフォルトは各ターゲットがロードバランサーからトラフィックを受信するポート トラフィックポート
パス(HealthCheckPath) ヘルチェックのターゲット送信先であるpingパス。デフォルトは/ /index.html
タイムアウト時間 ヘルスチェックを失敗とみなす、ターゲットからレスポンスのない時間範囲は2~120秒 5秒
正常のしきい値(HealthyThresholdCount) 異常なターゲットが正常とみなされるまでに必要なヘルスチェックの回数 5
非正常なしきい値(UnhealthyThresholdConut) ターゲットが異常とみなされるまでに必要なヘルスチェックの回数 2
間隔 個々のターゲットのヘルスチェックの概算期間 30秒
成功コード ターゲットから正常なレスポンスを確認するために使用するHTTPコード 200

運用のモニタリング【ALB/NLB/CLB】

cloudWatchによりELBのメトリクスを60秒間隔で監視可能
統計情報として平均(average)、合計(sum)、最大値(max)、最小値(min)などを表示できるが各メトリクスによってどの統計で監視するかが適切かはことなるので注意

設定 説明 設定例
HealthHostCount 正常なバックエンドホスト数
UnHealthHostCount 異常なバックエンドホスト数
RequestCount リクエスト数
Latency 遅延時間
HTTPCode_ELB4XX、HTTPCode_ELB5XX ELBが返した4XX,5XXのレスポンス数
HTTPCode_Backend_2XX,HTTPCode_Backed_3XX,HTTPCode_Backed_4XX,HTTPCode_5XX バックエンドが返した2XX,3XX4XX,5XXレスポンス数
BackendConnectionErrors バックエンドへの接続エラーの回数
SurgeQueueLength バックエンドへの保留中の件数
SpilloverCount キュー溢れのため拒否した件数

アクセスログの記録【ALB/NLB/CLB】

最短5分間隔でELBのアクセスログを取得可能
指定したS3バケットに簡単にログを自動保管
ELBの種類によってアクセスログの出力フィールドも異なる

コネクション

コネクションタイムアウト【ALB/NLB/CLB】

無通信状態が続くとコネクションが自動で切断する

ALB NLB CLB
デフォルト:60秒 350秒固定 デフォルト:60秒

ALB/CLBのコネクションタイムアウト値は変更が可能
1~4000秒の間で自由に設定可能

Connection Draining(登録解除の遅延)【ALB/NLB/CLB】

バックエンドからEC2インスタンスをELBから登録解除したり、ヘルスチェックが失敗した際に新規リクエストの割り振りは中止して、処理中のリクエストは終わるまで一定期間待つ
・全てのELBでデフォルトで有効、タイムアウト300秒
・タイムアウト最大3600秒

スティッキーセッション【ALB/CLB】

同じユーザーからきたリクエストをすべて同じEC2インスタンスに送信

・アプロケーションでのsession情報、一時ファイルなどをEC2インスタンスに保持する構成の場合に必要
・デフォルトでは無効、利用するためには有効にする
・HTTP/HTTPSでのみ利用可能

スティッキーセッションを使わない設計が理想
EC2インスタンスの増減を柔軟に対応できるように、session情報などは別のDBサーバーやキャッシュサーバーに持たせる設計が望ましいため。
この場合スティッキーセッションは不要。

ロードバランサーによって生成されるクッキーを使用したスティッキーセッション【ALB/CLB】

・session開始からの有効期間を指定してELBで制御
・無制限にすることも可能(無制限でもブラウザを閉じれば終了)

アプリケーション生成のクッキーを使用したスティッキーセッション【CLB】

・アプリケーションが作成したcookieに合わせる
・アプリケーションが作成するcookie名を指定

セキュリティ関連

SSL/TLS Termination

ELB側でSSL/TLS認証ができる

通信パターン
1.ELBでSSL Terminationし、バックエンドとはSSLなし
2.ELBでSSL Terminationし、バックエンドとは別SSL
3.SSLをバイパスしてバックエンドTCPで送信

ユーザー→ELB ELB→バックエンド
HTTPS HTTP
HTTP/SSL HTTPS/SSL
TCP TCP

HTTPS/SSL利用時のTLSサーバー証明書【ALB/NLB/CLB】

AWS Certificate Manager(ACM)を使用すれば証明書のリクエスト、管理更新、プロビジョニングが容易に実行可能
・無料で証明書を利用可能(ACMと統合されているELB、Amazon cloud front,Amazon API Gateway)に対してのみ
・証明書は自動更新されるので失効の心配がない
・ACMで発行される証明書はドメイン認証タイプの証明書なのでより上位の証明書(OV,EV)を利用する場合にはサードパーティ製の証明書を取得してインポートして利用

SNIでの複数TLS証明書のスマートセクション【ALB/NLB】

複数の証明書を一つのALB/NLBのListenerに設定可能
・SNIをサポートするクライアントには、適正な証明書を選択してTLSで通信をできる

ALB毎に最大25 証明書まで(デフォルトを除く)
・ACMまたはIAMの全ての証明書が利用可能

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

AWS Glue で pg8000 driver を使って PostgreSQL の RDS インスタンスに接続する

Overview

AWS Glue のスクリプトで、DynamicFrameWriter を使ってできることより、もう少し色々細かいことをやりたかったので、PostgreSQL の RDS インスタンスに Python のライブラリで接続したかった。

その手順メモ。

なお、Glue では pure-Python なライブラリしか使えない。例えば pandas のような C library は未サポート。なので pg8000 を使う。

Procedure

手順

  1. pg8000, scramp (依存で必要) の tar ファイルをダウンロード
  2. tar を解凍して zip を作る
  3. zip を s3 に置く
  4. Glue job の設定、Python library path に s3 のパスを入力

詳細は以下

1. pg8000, scramp (依存で必要) の tar ファイルをダウンロード

pypi から tar がダウンロードできるのでローカルに落とす。

(pypi > "Download files")

以下直リンク

2. tar を解凍して zip を作る

落としてきた tar を解凍すると、中にライブラリ本体のソースコードがあるのでそれを抜いて zip にする。

pg8000

$ tar xzf pg8000-1.16.5.tar.gz --strip-components 1 pg8000-1.16.5/pg8000/
$ zip -r pg8000.zip pg8000
  adding: pg8000/ (stored 0%)
  adding: pg8000/_version.py (deflated 31%)
  adding: pg8000/__init__.py (deflated 58%)
  adding: pg8000/core.py (deflated 76%)
  adding: pg8000/exceptions.py (deflated 77%)
  adding: pg8000/converters.py (deflated 74%)

scramp

$ tar xzf scramp-1.2.0.tar.gz --strip-components 1 scramp-1.2.0/scramp/
$ zip -r scramp.zip scramp
  adding: scramp/ (stored 0%)
  adding: scramp/_version.py (deflated 31%)
  adding: scramp/__init__.py (deflated 41%)
  adding: scramp/core.py (deflated 75%)
  adding: scramp/utils.py (deflated 56%)

3. zip を s3 に置く

ここでは仮に以下の場所に置いたとする。

  • s3://tommarute/python/lib/pg8000.zip

  • s3://tommarute/python/lib/scramp.zip

4. Glue job の設定、Python library path に s3 のパスを入力

以下に入力。

glu-python-lib.png

複数あるのでカンマで区切る。

s3://tommarute/python/lib/pg8000.zip,s3://tommarute/python/lib/scramp.zip

これで設定は完了

使う

後は普通に import して使えばOK

例えばこんな感じ

import pg8000

DSN = {
    'host': 'postgres.xxxx.us-east-1.rds.amazonaws.com',
    'port': 5432,
    'database': 'ec',
    'user': 'tommarute',
    'password': 'secret'
}

table = 'my_contact'

with pg8000.connect(**DSN) as con:
    res = con.run("SELECT count(*) FROM information_schema.tables WHERE table_name = :table", table=table)
    table_count = res[0][0]
    print(f'table_count of {table} = {table_count}')

使い方のサンプルは pg8000 の Github リポジトリに豊富に載っている。

なお、Glue の jupyter notebook で使いたい場合は、Dev endpoints の設定で上記 4 で行った設定をすればいける。

参考

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

【axios+SAM+API Gateway】apiを叩こうとしたらめちゃんこハマった件 (1/3)`SAM`を利用して、`API Gateway`及び`lambda`を構築

はじめに

APIの経験がないから、試してみたい
axiosというのがいいらしい

コーディングに注力したい(楽をしたい)
AWS API Gatewayを使おう

どうせなら構成はコード管理したい
SAMを使おう

というわけで以下の構成を考えてみました。
image.png

フロント及びバックエンド両方とも処理部分はとてもシンプルなものにしたのですが、
それでも「SAMってどうやるの?」「GETメソッドがローカルから叩けない…」「GET
できたと思ったら、今度はPOSTが叩けない…」等々でだいぶハマりました。

本稿では「ローカルからGET及びPOSTメソッドのAPIを叩く」までを振り返り、
私が躓いたところをまとめようと思います。

※開発環境はMacです。

やったこと

今回私は以下のような流れで進めました。

1. SAMを利用して、API Gateway及びlambdaを構築
1. ローカルから先程作成したAPI GatewayGETメソッドを叩く
1. ローカルから先程作成したAPI GatewayPOSTメソッドを叩く

今回は各ステップの内容について計3回に分けて触れていきたいと思います。

1. SAMを利用して、API Gateway及びlambdaを構築

SAMとは、Serverless Application Modelの略で、サーバーレスアプリケーション構築用のオープンソースフレームワークです。CloudFormationのサーバーレスリソース用に特化したようなもので、CloudFormationと併用する事もできます。これを利用することで、各リソースをコード管理することができます。

SAMを利用するためには、事前にAWS SAM CLIをインストールする必要があります(公式ドキュメントはこちら)。その後、sam initを入力し、ウィザードに従って初期化します。以下に私が今回実行した例を載せます。

$sam init
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1 ←1を選ぶといい感じの雛形を作ってくれるので、最初はこっちのほうがいいと思います。

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.7
    4 - go1.x
    5 - java11
    6 - dotnetcore3.1
    7 - nodejs10.x
    8 - python3.7
    9 - python3.6
    10 - python2.7
    11 - ruby2.5
    12 - java8
    13 - dotnetcore2.1
Runtime: 8 ←ランタイムを決めるところ。お好きなのをどうぞ。

Project name [sam-app]: demo-sam-app ←今回SAMで作成するプロジェクト名。

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
Template selection: 1 ←どんなテンプレートを利用するか。今回は1を利用

-----------------------
Generating application:
-----------------------
Name: demo-sam-app
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./demo-sam-app/README.md

そうすると、先程入力したプロジェクト名でディレクトリが作成されているので、そこに移動すると、以下の構成になっています。

demo-sam-app/
           ┃━events/
           ┃      ┗event.json
           ┃
           ┃━hello_world/
           ┃           ┃━__init__.py
           ┃           ┃
           ┃           ┃━app.py
           ┃           ┃
           ┃           ┗requirement.txt
           ┃
           ┃━test/
           ┃    ┗unit/
           ┃        ┃━__init__.py
           ┃        ┃
           ┃        ┗test_handler.py
           ┃
           ┃
           ┃━.gitignore
           ┃
           ┃━README.md
           ┃
           ┗template.yaml

重要なのは、template.yamlでここでどんなアプリケーションの構成にするか記述します。
以下にResources部分を抜粋します。

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

要点をかいつまんで説明すると、

  • Type: AWS::Serverless::Function 本リソースの種類。今回はlambda
  • CodeUri: hello_world/ 本リソースを構成するコードはどこにあるか(今回は./hello_world)。
  • Type: Api リソース属性。今回はApiとなっているので、「この関数はAPI Gatewayを介して呼ばれるものですよ」ということを言っている。こうすると勝手に対応したAPI Gatewayを作成してくれるので便利!
  • Path: /hello APIのパス(URLの指定)。
  • Method: get APIのメソッド。

という感じです。
では、本リソースをビルド・デプロイしたいと思います。ビルドには sam build、デプロイにはsam deployを利用します。初回デプロイはsam deploy --guidedとしたほうがいいです。sam deploy --guidedとしたときにいくつかの選択肢がでます。ここでSave arguments to samconfig.toml [Y/n]: yとすることで、次回以降今回入力した情報を引き継いでくれるので、--guidedが不要になります。

$ sam deploy --guided
Looking for samconfig.toml :  Not found

Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: demo-sam-app
AWS Region [us-east-1]: ap-northeast-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to samconfig.toml [Y/n]: y ←これ

これでテンプレートにかかれた内容を元に、API Gateway及びlambdaが作成されます。
image.png

では今回作成したAPIcurlを利用して叩いてみたいと思います。

$ curl https://{メソッドのURL}/Prod/hello
{"message": "hello world!"}

確かにメッセージが返ってきたことが確認できました。

終わりに

今回はSAMを使ったAPI環境の構築を行いました。次回はlocalhostを立てて、そこからAPIを叩いた際に直面した問題について触れたいと思います。

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

【axios+SAM+API Gateway】localhostからapiを叩けるようになるために苦労した話 (1/3)SAMを利用して、API Gateway及びlambdaを構築

はじめに

APIの経験がないから、試してみたい
axiosというのがいいらしい

コーディングに注力したい(楽をしたい)
AWS API Gatewayを使おう

どうせなら構成はコード管理したい
SAMを使おう

というわけで以下の構成を考えてみました。
image.png

フロント及びバックエンド両方とも処理部分はとてもシンプルなものにしたのですが、
それでも「SAMってどうやるの?」「GETメソッドがローカルから叩けない…」「GET
できたと思ったら、今度はPOSTが叩けない…」等々でだいぶハマりました。

本稿では「ローカルからGET及びPOSTメソッドのAPIを叩く」までを振り返り、
私が躓いたところをまとめようと思います。

※開発環境はMacです。

やったこと

今回私は以下のような流れで進めました。

1. SAMを利用して、API Gateway及びlambdaを構築
1. ローカルから先程作成したAPI GatewayGETメソッドを叩く
1. ローカルから先程作成したAPI GatewayPOSTメソッドを叩く

今回は各ステップの内容について計3回に分けて触れていきたいと思います。

1. SAMを利用して、API Gateway及びlambdaを構築

SAMとは、Serverless Application Modelの略で、サーバーレスアプリケーション構築用のオープンソースフレームワークです。CloudFormationのサーバーレスリソース用に特化したようなもので、CloudFormationと併用する事もできます。これを利用することで、各リソースをコード管理することができます。

SAMを利用するためには、事前にAWS SAM CLIをインストールする必要があります(公式ドキュメントはこちら)。その後、sam initを入力し、ウィザードに従って初期化します。以下に私が今回実行した例を載せます。

$sam init
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1 ←1を選ぶといい感じの雛形を作ってくれるので、最初はこっちのほうがいいと思います。

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.7
    4 - go1.x
    5 - java11
    6 - dotnetcore3.1
    7 - nodejs10.x
    8 - python3.7
    9 - python3.6
    10 - python2.7
    11 - ruby2.5
    12 - java8
    13 - dotnetcore2.1
Runtime: 8 ←ランタイムを決めるところ。お好きなのをどうぞ。

Project name [sam-app]: demo-sam-app ←今回SAMで作成するプロジェクト名。

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
Template selection: 1 ←どんなテンプレートを利用するか。今回は1を利用

-----------------------
Generating application:
-----------------------
Name: demo-sam-app
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./demo-sam-app/README.md

そうすると、先程入力したプロジェクト名でディレクトリが作成されているので、そこに移動すると、以下の構成になっています。

demo-sam-app/
           ┃━events/
           ┃      ┗event.json
           ┃
           ┃━hello_world/
           ┃           ┃━__init__.py
           ┃           ┃
           ┃           ┃━app.py
           ┃           ┃
           ┃           ┗requirement.txt
           ┃
           ┃━test/
           ┃    ┗unit/
           ┃        ┃━__init__.py
           ┃        ┃
           ┃        ┗test_handler.py
           ┃
           ┃
           ┃━.gitignore
           ┃
           ┃━README.md
           ┃
           ┗template.yaml

重要なのは、template.yamlでここでどんなアプリケーションの構成にするか記述します。
以下にResources部分を抜粋します。

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

要点をかいつまんで説明すると、

  • Type: AWS::Serverless::Function 本リソースの種類。今回はlambda
  • CodeUri: hello_world/ 本リソースを構成するコードはどこにあるか(今回は./hello_world)。
  • Type: Api リソース属性。今回はApiとなっているので、「この関数はAPI Gatewayを介して呼ばれるものですよ」ということを言っている。こうすると勝手に対応したAPI Gatewayを作成してくれるので便利!
  • Path: /hello APIのパス(URLの指定)。
  • Method: get APIのメソッド。

という感じです。
では、本リソースをビルド・デプロイしたいと思います。ビルドには sam build、デプロイにはsam deployを利用します。初回デプロイはsam deploy --guidedとしたほうがいいです。sam deploy --guidedとしたときにいくつかの選択肢がでます。ここでSave arguments to samconfig.toml [Y/n]: yとすることで、次回以降今回入力した情報を引き継いでくれるので、--guidedが不要になります。

$ sam deploy --guided
Looking for samconfig.toml :  Not found

Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: demo-sam-app
AWS Region [us-east-1]: ap-northeast-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to samconfig.toml [Y/n]: y ←これ

これでテンプレートにかかれた内容を元に、API Gateway及びlambdaが作成されます。
image.png

では今回作成したAPIcurlを利用して叩いてみたいと思います。

$ curl https://{メソッドのURL}/Prod/hello
{"message": "hello world!"}

確かにメッセージが返ってきたことが確認できました。

終わりに

今回はSAMを使ったAPI環境の構築を行いました。次回はlocalhostを立てて、そこからAPIを叩いた際に直面した問題について触れたいと思います。

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

【非エンジニアでも合格】ゼロから始めるAWS認定ソリューションアーキテクト - アソシエイト勉強方法

0. 目次

1.はじめに
2.前提知識
3.教材
4.学習方法
5.本試験
6.あとがき

1. はじめに

こんにちは。インフラエンジニアへの転職活動中のmsstgcと申します。
AWSとインフラについての全体像を知るためにAWS Certified Solutions Architect - Associate【SAA-C02】を受験してきました。
AWSはおろかインフラ知識0の未経験者でも一発合格できましたので、
私と同じように業界未経験の方はもちろん、新入社員で資格を取得する必要がある方などの参考になれば幸いです。

2. 前提知識

  • ヲタクの会社で事務をやっています。要は知識0、畑違いのIT未経験者です。
  • AWS認定試験の勉強を始める直前にLPICレベル1に合格しています。
  • 勉強期間は1ヶ月半(100時間くらい)、後述しますが試験に合格するだけなら不要なところまでやったので短縮は可能です。

3. 教材

主要なサービス一覧

2020年9月時点の内容となります。
ちょくちょく更新されているので、最新のものを確認してください。
これ以外のサービスについては、気になるものがあれば適時読む感じでいいと思います。
※クラスメソッドさんの2回受験して得たAWS認定ソリューションアーキテクト(SAA)の教訓
を参考にしています。

4. 学習方法

学習方針

個人的には資格試験はあくまで網羅的に知識をインプットすることが目的なので、合格することを第一に考えた学習方法となっています。
実務で必要なら合格した後にじっくり触ればOKという考えです。

学習の流れ

試験範囲全体の基礎を学び、後はひたすら模擬試験を解く。それだけ

  1. 基礎知識のインプット
    これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(SAA-C02試験対応版)を一通りやります。
    動画時間だけで26時間+ハンズオンをまでしっかりやるとかなりの時間が必要になり、最初の内容はどうせ忘れてしまうので、サラッと流す感じで大丈夫です。
    私はすべてのハンズオンを行いましたが、やらなくてもいいと思います。
    書籍を用意できる方は、Udemyでやった部分を書籍で復習すると、お互いを補完できていい感じです。

  2. 模擬試験
    Udemyの模擬試験が8回分あるので、ひたすら回しながら足りない知識をBlackBeltやGoogleで補完していきます。
    答えを暗記するのではなく、なぜそうなるのかを理解するようにしましょう。
    私の場合はすべての模試を3周解いて、正答率が90%~95%くらいでした。

学習の際に意識すること

試験合格のために重要なのは主要なサービスの内容と、それらをどのように組み合わせて設計をするか(ベストプラクティス)をしっかり覚えることです。
これだけで合格ラインの7割に十分届きます。

そのため、試験の合格のみを考えるなら【SAA-C02版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)の高難度の模擬試験に1,2回しかでてこないような単語、サービスは覚えなくても問題ありません。
多少の偏りはあると思いますが、私が受けた時は主要なサービスについての問題しか出ませんでした。

覚えるべきサービスに優先順位をつけるとこんな感じです。

  1. 主要なサービス ← 必須
  2. 模擬試験でよく見るサービス ← 余裕があれば
  3. 模擬試験全体を通して1,2回しか出てこないサービス ← 不要

5. 本試験

この手の試験全般に言えることですが、試験問題の日本語が不自由すぎてつらいです。
試験時間は130分で、当初の予定では60分くらいで1周できると思っていたのですが、
一通り問題を解き終わった時点で残り20分。。。
半分くらい見直したところで時間切れとなりました。
正直手ごたえはなかったのですが、結果は・・・合格!
試験が終わって3時間後くらいには詳細な点数が出ていて、[798/1000]でした。
合格ラインが720点なのでそこそこ余裕があったのではないでしょうか。

6. あとがき

試験の難易度としては簡単とはいいませんが、
すべて選択問題なのでそこまで気負う必要もありません。
知識0からの方でも一発合格は十分可能だと思います!

AWS Certified Solutions Architect - Associate【SAA-C02】の勉強を通して、
AWSについての基礎知識、インフラとはどういったものなのかがなんとなくわかった気がします。
ただ、あくまで知識がついただけなので、今後は実際に自分で書いたコードをAWSで実装しながら、
もっと理解を深めていきたいです。

最後にTwitterもやっていますので、ぜひフォローしていただけると嬉しいです!

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

AWS Network資格に合格しました

はじめに

今日(9/21)に「AWS Certified Advanced Networking - Specialty」資格(以降ANS)に受かったので、勉強に使った教材や試験の所感を書いていきます。
最初に白状しておくと、自分は9/6に一度受けて落ちているので、2回目の受験での合格でした。
自分みたいにネットワーク回り(ルーターやスイッチなど)をまったくやっていない人や、Direct Connectの構築をやったことのない人にはちょっと頑張りが必要な試験でした。
誰かの役に立てば幸いです。

※過去の記事
AWS SAAに受かった話
AWS SOAに受かった話
AWS DVA(とCLP)に受かった話
AWS SAPに受かった話
AWS SCSに受かった話

獲得スコア

受験日 スコア/合格点/満点 結果
2020/9/6 62%/65%/100% 不合格
2020/9/21 72%/65%/100% 合格

勉強に使った教材

※いつものようにBlackbeltとWhizlabsは省略
※また、ANSだけは公式の模擬試験が無いです(スコアレポートの表示方法も違うので、管理している団体が違うとかそういう理由かも)

AWS Summit 2019資料

AWS認定資格は最新情報よりちょっとだけ内容が古いので、少し前のセッション動画/資料を見るのがおすすめです。
(Blackbeltでも可)

https://aws.amazon.com/jp/summits/tokyo-osaka-2019-report/

※この中でも「ネットワークデザインパターン Deep Dive」というセッションはこれまでのアップデートの歴史から触れてくれているので、非常に参考になりました。

AWS公式ドキュメント

後でも触れますが、Direct ConnectやVPNの設定方法はかなり詳しく問われるので、コンソールでの設定方法(手順や入力項目)や接続完了までの流れは公式ドキュメントを読み込むようにしましょう。
文字だけではわかりにくかったりするので、「Direct Connect 作成」などで検索して、コンソール画面を貼ってくれている人の記事などを読んだりしました。

Direct Connectハンズオンセミナー

Direct Connectは実際に試すのが難しいですが、定期的にハンズオンセミナーが開催されています。
実際に手を動かす機会でもあるので試験を受ける人はなるべく参加した方が良いと思います。
(自分はタイミングが合わずに参加できませんでしたが)
https://aws.amazon.com/jp/dx_labo/

試験の所感

  • 試験自体がTransit GatewayやDirect Connect Gateway、Route53 Resolverが出る前の世界線
    • それらが無い時はインスタンスを立ててやっていたと思うと、AWSのサービスリリース方針が見えてきて面白い
  • ネットワークの話は思ったより細かく出る
    • 実際の設定手順は知らなくても良いが、それぞれの設定がどういうもので、いつ使われるかは知っておく
  • Direct Connectの作成手順は細かく問われる(専用線接続もホスト接続も)
    • LOA-CFAはいつどうやってもらえるのか、もらった後はどうするのか、等々
  • VPCフローログの使われる場面(できることとできないこと)
  • 複数のVPCがある構成で、どことどこを何で接続するか、という問題が多い
    • VPCピアリングをするのかVPN接続をするのか。はたまたDirect Connectで繋ぐのかなど
  • オンプレの名前解決をAWS側のDNSで解決する構成(もしくは逆の構成)
  • Cloudformationは普段から業務で使っている人、あるいはDOP試験に受かる知識があれば準備不要
  • 問題文はよく読む(重要)
    • 問題文で「Direct Connectを構成する」と書いてあるのに選択肢にVPNが紛れていたりする(逆も然り)
    • オンプレとAWSの接続要件が具体的には「プライベート」なのか「暗号化」なのか
    • (Direct Connectはプライベートではあるものの通信の暗号化はしていない点に注意)
  • 字面だけだと構成図が浮かびにくいので、(SAPほどではないものの)普通にやると結構時間がかかる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitLab Helm Chartで相対URLを可能にする

はじめに

公式のGitLab Helm Chartでは相対URLが使用できない制限があります。
今回はHelm Chartの中身を一部修正して、相対URLを可能にする手順を説明します。
AWS環境で確認します。

環境情報

EKS 1.16
Helm 2.14.3
Helmfile 0.122.0
GitLab Helm Chart 2.3.7
kube2iam Helm Chart 2.5.0
ALB Ingress Controller Helm Chart 0.1.11
NGINX Ingress Controller Helm Chart 1.39.0

手順

EKS, S3, RDS, ALB Ingress ControllerとS3アクセスのためのIAM Roleは既に作成されているものとします。
GitLabとS3の連携に関してはこちらを参考にしてください。
ALB Ingress ControllerとNGINX Ingress Controllerの組み合わせに関してはこちらを参考にしてください。

必要なKubernetesリソースのデプロイ

kube2iam, ALB Ingress Controller, NGINX Ingress ControllerをHelmfileでデプロイします。
NGINX Ingress Controllerを使用する理由は、rewrite targetによるリダイレクト処理を行いたいからです。

helmfile.yaml
repositories:
  - name: stable
    url: https://kubernetes-charts.storage.googleapis.com
  - name: incubator
    url: https://kubernetes-charts-incubator.storage.googleapis.com

releases:
  - name: kube2iam
    namespace: kube-system
    chart: stable/kube2iam
    version: 2.5.0
    values:
      - host:
          iptables: true
          interface: eni+
        extraArgs:
          auto-discover-base-arn: ""
        rbac:
          create: true
  - name: aws-alb-ingress-controller
    namespace: kube-system
    chart: incubator/aws-alb-ingress-controller
    version: 0.1.11
    values:
      - clusterName: test-cluster
        autoDiscoverAwsRegion: true
        autoDiscoverAwsVpcID: true
        podAnnotations:
          iam.amazonaws.com/role: aws-alb-ingress-controller
  - name: nginx-ingress
    namespace: kube-system
    chart: stable/nginx-ingress
    version: 1.39.0
    values:
      - controller:
          service:
            type: NodePort
$ helmfile apply -f ./helmfile.yaml

次に、ALBからNGINX Ingress ControllerへルーティングさせるためのIngressを作成します。
全リクエストを通過するようにします。

alb-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    kubernetes.io/ingress.class: alb
  name: alb-ingress
  namespace: kube-system
spec:
  rules:
  - http:
      paths:
      - backend:
          serviceName: nginx-ingress-controller
          servicePort: 80
$ kubectl apply -f ./alb-ingress.yaml

GitLab Helm Chartの編集

templatesの中身を修正する必要があるので、次のコマンドで手元にHelm Chartを用意します。

$ helm fetch gitlab/gitlab --version 2.3.7

templates

まずはIngressを編集します。
assetsの対応をしないとcss等を読み込むことができないのでIngressのrewrite targetで/gitlab/assets/assetsにリダイレクトさせます。

gitlab/charts/gitlab/charts/unicorn/templates/ingress.yaml
{{- if .Values.enabled -}}
{{- if eq (include "gitlab.ingress.enabled" $) "true" -}}
{{- $gitlabHostname := include "gitlab.gitlab.hostname" . -}}
{{- $tlsSecret := include "unicorn.tlsSecret" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ template "fullname" . }}
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
  annotations:
    kubernetes.io/ingress.class: "{{ template "gitlab.ingressclass" . }}"
    kubernetes.io/ingress.provider: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.ingress.proxyBodySize | quote }}
    nginx.ingress.kubernetes.io/proxy-read-timeout: {{ .Values.ingress.proxyReadTimeout | quote }}
    nginx.ingress.kubernetes.io/proxy-connect-timeout: {{ .Values.ingress.proxyConnectTimeout | quote }}
    {{ include "gitlab.certmanager_annotations" . }}
  {{- range $key, $value := merge .Values.ingress.annotations .Values.global.ingress.annotations }}
    {{ $key }}: {{ $value | quote }}
  {{- end }}
spec:
  rules:
    - host: {{ $gitlabHostname }}
      http:
        paths:
-         - path: /
+         - path: /gitlab
            backend:
              serviceName: {{ template "fullname" . }}
              servicePort: {{ .Values.service.workhorseExternalPort }}
-         - path: /admin/sidekiq
+         - path: /gitlab/admin/sidekiq
            backend:
              serviceName: {{ template "fullname" . }}
              servicePort: {{ .Values.service.externalPort }}
+         - path: /assets
+           backend:
+             serviceName: {{ template "fullname" . }}
+             servicePort: {{ .Values.service.workhorseExternalPort }}
  {{- if (and $tlsSecret (eq (include "gitlab.ingress.tls.enabled" $) "true" )) }}
  tls:
    - hosts:
      - {{ $gitlabHostname }}
      secretName: {{ $tlsSecret }}
  {{- else }}
  tls: []
  {{- end }}
+ ---
+ apiVersion: extensions/v1beta1
+ kind: Ingress
+ metadata:
+   name: {{ template "fullname" . }}-assets
+   namespace: {{ $.Release.Namespace }}
+   annotations:
+     nginx.ingress.kubernetes.io/rewrite-target: /assets/$1
+     {{ include "gitlab.certmanager_annotations" . }}
+   {{- range $key, $value := merge .Values.ingress.annotations .Values.global.ingress.annotations }}
+     {{ $key }}: {{ $value | quote }}
+   {{- end }}
+ spec:
+   rules:
+     - host: {{ $gitlabHostname }}
+       http:
+         paths:
+           - path: /gitlab/assets/(.*)
+             backend:
+               serviceName: {{ template "fullname" . }}
+               servicePort: {{ .Values.service.workhorseExternalPort }}
{{- end -}}
{{- end -}}

unicornのConfigMapで/gitlabでアクセスするための設定を入れます。

gitlab/charts/gitlab/charts/unicorn/templates/configmap.yaml
{{- if .Values.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "fullname" . }}
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
data:
  installation_type: |
    gitlab-helm-chart
  database.yml.erb: |
    production:
      adapter: postgresql
      encoding: unicode
      database: {{ template "gitlab.psql.database" . }}
      pool: 10
      username: {{ template "gitlab.psql.username" . }}
      password: "<%= File.read("/etc/gitlab/postgres/psql-password").strip.dump[1..-2] %>"
      host: {{ template "gitlab.psql.host" . }}
      port: {{ template "gitlab.psql.port" . }}
      prepared_statements: {{ template "gitlab.psql.preparedStatements" . }}
      # load_balancing:
      #   hosts:
      #     - host1.example.com
      #     - host2.example.com
{{- include "gitlab.psql.ssl.config" . | indent 6 }}
  smtp_settings.rb: |
{{ include "gitlab.smtp_settings" . | indent 4 }}
  resque.yml.erb: |
    production:
      # Redis (single instance)
      url: {{ template "gitlab.redis.url" . }}
      id:
  unicorn.rb: |
    # This file should be equivalent to `unicorn.rb` from:
    # * gitlab-foss: https://gitlab.com/gitlab-org/gitlab-foss/blob/master/config/unicorn.rb.example
    # * omnibus: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/unicorn.rb.erb
    worker_processes {{ .Values.workerProcesses }}
    working_directory "/srv/gitlab"
    listen "0.0.0.0:{{ .Values.service.internalPort }}", :tcp_nopush => true
    timeout {{ .Values.workerTimeout }}
    pid "/home/git/unicorn.pid"
    preload_app true

    require_relative "/srv/gitlab/lib/gitlab/cluster/lifecycle_events"

    before_exec do |server|
      # Signal application hooks that we're about to restart
      Gitlab::Cluster::LifecycleEvents.do_master_restart
    end

    run_once = true
    before_fork do |server, worker|
      if run_once
        # There is a difference between Puma and Unicorn:
        # - Puma calls before_fork once when booting up master process
        # - Unicorn runs before_fork whenever new work is spawned
        # To unify this behavior we call before_fork only once (we use
        # this callback for deleting Prometheus files so for our purposes
        # it makes sense to align behavior with Puma)
        run_once = false

        # Signal application hooks that we're about to fork
        Gitlab::Cluster::LifecycleEvents.do_before_fork
      end

      # The following is only recommended for memory/DB-constrained
      # installations.  It is not needed if your system can house
      # twice as many worker_processes as you have configured.
      #
      # This allows a new master process to incrementally
      # phase out the old master process with SIGTTOU to avoid a
      # thundering herd (especially in the "preload_app false" case)
      # when doing a transparent upgrade.  The last worker spawned
      # will then kill off the old master process with a SIGQUIT.
      old_pid = "#{server.config[:pid]}.oldbin"
      if old_pid != server.pid
        begin
          sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
          Process.kill(sig, File.read(old_pid).to_i)
        rescue Errno::ENOENT, Errno::ESRCH
        end
      end
      #
      # Throttle the master from forking too quickly by sleeping.  Due
      # to the implementation of standard Unix signal handlers, this
      # helps (but does not completely) prevent identical, repeated signals
      # from being lost when the receiving process is busy.
      # sleep 1
    end

    after_fork do |server, worker|
      # Signal application hooks of worker start
      Gitlab::Cluster::LifecycleEvents.do_worker_start

      # per-process listener ports for debugging/admin/migrations
      # addr = "127.0.0.1:#{9293 + worker.nr}"
      # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
    end

    ENV['GITLAB_UNICORN_MEMORY_MIN'] = ({{ int .Values.memory.min }} * 1 << 20).to_s
    ENV['GITLAB_UNICORN_MEMORY_MAX'] = ({{ int .Values.memory.max }} * 1 << 20).to_s
+   ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"

  gitlab.yml.erb: |
    production: &base
      gitlab:
        host: {{ template "gitlab.gitlab.hostname" . }}
        https: {{ hasPrefix "https://" (include "gitlab.gitlab.url" .) }}
+       relative_url_root: /gitlab
        {{- with .Values.global.hosts.ssh }}
        ssh_host: {{ . | quote }}
        {{- end }}
        {{- with .Values.global.appConfig }}
        impersonation_enabled: {{ .enableImpersonation }}
        usage_ping_enabled: {{ eq .enableUsagePing true }}
        default_can_create_group: {{ eq .defaultCanCreateGroup true }}
        username_changing_enabled: {{ eq .usernameChangingEnabled true }}
        issue_closing_pattern: {{ .issueClosingPattern | quote }}
        default_theme: {{ .defaultTheme }}
        {{- include "gitlab.appConfig.defaultProjectsFeatures.configuration" $ | nindent 8 }}
        webhook_timeout: {{ .webhookTimeout }}
        {{- end }}
        trusted_proxies:
        {{- if .Values.trusted_proxies }}
{{ toYaml .Values.trusted_proxies | indent 10 }}
        {{- end }}
        time_zone: {{ .Values.global.time_zone | quote }}
        email_from: {{ template "gitlab.email.from" . }}
        email_display_name: {{ .Values.global.email.display_name | quote }}
        email_reply_to: {{ template "gitlab.email.reply_to" . }}
        email_subject_suffix: {{ .Values.global.email.subject_suffix | quote }}
      {{- with .Values.global.appConfig }}
      {{- if eq .incomingEmail.enabled true }}
{{ include "gitlab.appConfig.incoming_email" . | indent 6 }}
      {{- end }}
      {{- include "gitlab.appConfig.cronJobs" . | nindent 6 }}
      gravatar:
        plain_url: {{ .gravatar.plainUrl }}
        ssl_url: {{ .gravatar.sslUrl }}
{{ include "gitlab.appConfig.extra" . | indent 6 }}
      {{- end }}
      {{- include "gitlab.appConfig.artifacts.configuration" (dict "config" $.Values.global.appConfig.artifacts "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.lfs.configuration" (dict "config" $.Values.global.appConfig.lfs "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.uploads.configuration" (dict "config" $.Values.global.appConfig.uploads "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.packages.configuration" (dict "config" $.Values.global.appConfig.packages "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.external_diffs.configuration" (dict "config" $.Values.global.appConfig.externalDiffs "context" $) | nindent 6 }}
      pages:
        enabled: false
      mattermost:
        enabled: false
      gitlab_ci:
      {{- include "gitlab.appConfig.ldap.configuration" $ | nindent 6 }}
      {{- include "gitlab.appConfig.omniauth.configuration" $ | nindent 6 }}
      kerberos:
        enabled: false
      shared:
{{ include "gitlab.appConfig.gitaly" . | indent 6 }}
{{ include "gitlab.appConfig.repositories" . | indent 6 }}
      backup:
        path: "tmp/backups"   # Relative paths are relative to Rails.root (default: tmp/backups/)
{{ include "gitlab.appConfig.shell" . | indent 6 }}
{{ include "gitlab.appConfig.shell.ssh_port" . | indent 8 }}
{{ include "gitlab.appConfig.shell.secret_file" . | indent 8 }}
      workhorse:
        secret_file: /etc/gitlab/gitlab-workhorse/secret
      git:
        bin_path: /usr/bin/git
      webpack:
      monitoring:
        ip_whitelist:
          {{- if kindIs "array" .Values.monitoring.ipWhitelist }}
          {{ toYaml .Values.monitoring.ipWhitelist | nindent 10 | trim }}
          {{- end }}
        sidekiq_exporter:
{{ include "gitlab.appConfig.rackAttack" . | indent 6 }}
      ## Registry Integration
      {{- include "gitlab.appConfig.registry.configuration" $ | nindent 6 }}
  configure: |
    {{- include "gitlab.scripts.configure.secrets" (dict) | nindent 4 -}}
    {{- include "gitlab.psql.ssl.initScript" . | nindent 4 }}
+ relative_url.rb: |
+   Rails.application.configure do
+     config.relative_url_root = "/gitlab"
+   end
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{.Release.Name }}-workhorse-config
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
data:
  installation_type: |
    gitlab-helm-chart
  workhorse-config.toml.erb: |
    [redis]
    URL = "{{ template "gitlab.redis.scheme" . }}://{{ template "gitlab.redis.host" . }}:{{ template "gitlab.redis.port" . }}"
    {{- if .Values.global.redis.password.enabled }}
    Password = "<%= File.read("/etc/gitlab/redis/password").strip.dump[1..-2] %>"
    {{- end }}
  configure: |
      set -e
      mkdir -p /init-secrets-workhorse/gitlab-workhorse
      cp -v -r -L /init-config/gitlab-workhorse/secret /init-secrets-workhorse/gitlab-workhorse/secret
      {{- if .Values.global.redis.password.enabled }}
      mkdir -p /init-secrets-workhorse/redis
      cp -v -r -L /init-config/redis/password /init-secrets-workhorse/redis/
      {{- end }}
# Leave this here - This line denotes end of block to the parser.
{{- end }}

unicornのDeploymentで新規追加するファイルをマウントさせるための設定を入れます。
また、相対パスにすることによりliveness, readinessが失敗するようになるので該当部分を削除します。
/tmpを追加するのはGitLab CI実行時にS3へartifactsアップロードに失敗しないようにするためです。

gitlab/charts/gitlab/charts/unicorn/templates/deployment.yaml
{{- if .Values.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ template "fullname" . }}
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
spec:
  {{- if .Values.global.operator.enabled }}
  paused: true
  {{- end }}
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ template "name" . }}
      release: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ template "name" . }}
        release: {{ .Release.Name }}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yml") . | sha256sum }}
        cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
      {{- range $key, $value := .Values.annotations }}
        {{ $key }}: {{ $value | quote }}
      {{- end }}
{{- if .Values.metrics.enabled }}
{{ toYaml .Values.metrics.annotations | indent 8 }}
{{- end }}
    spec:
      {{- if .Values.tolerations }}
      tolerations:
{{ toYaml .Values.tolerations | indent 8 }}
      {{- end }}
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
      {{- if eq (default .Values.global.antiAffinity .Values.antiAffinity) "hard" }}
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - topologyKey: "kubernetes.io/hostname"
              labelSelector:
                matchLabels:
                  app: {{ template "name" . }}
                  release: {{ .Release.Name }}
      {{- else if eq (default .Values.global.antiAffinity .Values.antiAffinity) "soft" }}
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            podAffinityTerm:
              topologyKey: kubernetes.io/hostname
              labelSelector:
                matchLabels:
                  app: {{ template "name" . }}
                  release: {{ .Release.Name }}
      {{- end }}
      initContainers:
{{ include "gitlab.extraInitContainers" . | indent 8 }}
{{ include "gitlab.certificates.initContainer" . | indent 8 }}
        - name: configure
          command: ['sh']
          args: [ '-c', 'sh -x /config-unicorn/configure ; sh -x /config-workhorse/configure']
          image: {{ .Values.init.image }}:{{ .Values.init.tag }}
          volumeMounts:
{{ include "gitlab.extraVolumeMounts" . | indent 10 }}
{{ include "gitlab.psql.ssl.volumeMount" . | indent 10 }}
          - name: unicorn-config
            mountPath: /config-unicorn
            readOnly: true
          - name: workhorse-config
            mountPath: /config-workhorse
            readOnly: true
          - name: init-unicorn-secrets
            mountPath: /init-config
            readOnly: true
          - name: unicorn-secrets
            mountPath: /init-secrets
            readOnly: false
          - name: workhorse-secrets
            mountPath: /init-secrets-workhorse
            readOnly: false
          resources:
{{ toYaml .Values.init.resources | indent 12 }}
        - name: dependencies
          image: "{{ coalesce .Values.image.repository (include "image.repository" .) }}:{{ coalesce .Values.image.tag (include "gitlab.versionTag" . ) }}"
          {{ template "gitlab.imagePullPolicy" . }}
          args:
            - /scripts/wait-for-deps
          env:
{{- if .Values.global.operator.enabled }}
            - name: BYPASS_SCHEMA_VERSION
              value: 'true'
{{- end }}
            - name: GITALY_FEATURE_DEFAULT_ON
              value: "1"
            - name: CONFIG_TEMPLATE_DIRECTORY
              value: '/var/opt/gitlab/templates'
            - name: CONFIG_DIRECTORY
              value: '/srv/gitlab/config'
            - name: WORKHORSE_ARCHIVE_CACHE_DISABLED
              value: "1"
          volumeMounts:
{{ include "gitlab.extraVolumeMounts" . | indent 12 }}
            - name: unicorn-config
              mountPath: '/var/opt/gitlab/templates'
            - name: unicorn-secrets
              mountPath: '/etc/gitlab'
              readOnly: true
          resources:
{{ toYaml .Values.init.resources | indent 12 }}
{{- include "pullsecrets" .Values.image | indent 6}}
      containers:
{{ include "gitlab.extraContainers" . | indent 8 }}
        - name: {{ .Chart.Name }}
          image: "{{ coalesce .Values.image.repository (include "image.repository" .) }}:{{ coalesce .Values.image.tag (include "gitlab.versionTag" . ) }}"
          {{ template "gitlab.imagePullPolicy" . }}
          ports:
            - containerPort: {{ .Values.service.internalPort }}
              name: unicorn
          env:
            - name: GITALY_FEATURE_DEFAULT_ON
              value: "1"
            - name: CONFIG_TEMPLATE_DIRECTORY
              value: '/var/opt/gitlab/templates'
            - name: CONFIG_DIRECTORY
              value: '/srv/gitlab/config'
{{- if .Values.metrics.enabled }}
            - name: prometheus_multiproc_dir
              value: /metrics
{{- end }}
{{- if .Values.workhorse.sentryDSN }}
            - name: GITLAB_WORKHORSE_SENTRY_DSN
              value: {{ .Values.workhorse.sentryDSN }}
{{- end }}
          volumeMounts:
{{- if .Values.metrics.enabled }}
            - name: unicorn-metrics
              mountPath: '/metrics'
{{- end }}
            - name: unicorn-config
              mountPath: '/var/opt/gitlab/templates'
            - name: unicorn-secrets
              mountPath: '/etc/gitlab'
              readOnly: true
            - name: unicorn-secrets
              mountPath: /srv/gitlab/config/secrets.yml
              subPath: rails-secrets/secrets.yml
            - name: unicorn-config
              mountPath: '/srv/gitlab/config/initializers/smtp_settings.rb'
              subPath: smtp_settings.rb
+           - name: unicorn-config
+             mountPath: '/srv/gitlab/config/initializers/relative_url.rb'
+             subPath: relative_url.rb
            - name: unicorn-config
              mountPath: '/srv/gitlab/INSTALLATION_TYPE'
              subPath: installation_type
            - name: shared-upload-directory
              mountPath: /srv/gitlab/public/uploads/tmp
              readOnly: false
            - name: shared-artifact-directory
              mountPath: /srv/gitlab/shared
              readOnly: false
+           - name: shared-tmp
+             mountPath: '/tmp'
+             readOnly: false
{{ include "gitlab.certificates.volumeMount" . | indent 12 }}
{{ include "gitlab.extraVolumeMounts" . | indent 12 }}
-         livenessProbe:
-           exec:
-             command:
-             - /scripts/healthcheck
-           initialDelaySeconds: 20
-           timeoutSeconds: 30
-           periodSeconds: 60
-         readinessProbe:
-           exec:
-             command:
-             - /scripts/healthcheck
-           timeoutSeconds: 2
          lifecycle:
            preStop:
              exec:
                command: ["/bin/bash", "-c", "pkill -SIGQUIT -f 'unicorn master'"]
          resources:
{{ toYaml .Values.resources | indent 12 }}
        - name: gitlab-workhorse
          image: "{{ coalesce .Values.workhorse.image (include "workhorse.repository" .) }}:{{ coalesce .Values.workhorse.tag (include "gitlab.versionTag" . ) }}"
          {{ template "gitlab.imagePullPolicy" . }}
          ports:
            - containerPort: {{ .Values.service.workhorseInternalPort }}
              name: workhorse
          env:
            - name: GITLAB_WORKHORSE_EXTRA_ARGS
              value: {{ .Values.workhorse.extraArgs | quote }}
            - name: GITLAB_WORKHORSE_LISTEN_PORT
              value: {{ default 8181 .Values.service.workhorseInternalPort | int | quote }}
            - name: CONFIG_TEMPLATE_DIRECTORY
              value: '/var/opt/gitlab/templates'
            - name: CONFIG_DIRECTORY
              value: '/srv/gitlab/config'
          volumeMounts:
            - name: workhorse-config
              mountPath: '/var/opt/gitlab/templates'
            - name: workhorse-secrets
              mountPath: '/etc/gitlab'
              readOnly: true
            - name: shared-upload-directory
              mountPath: /srv/gitlab/public/uploads/tmp
              readOnly: false
            - name: shared-artifact-directory
              mountPath: /srv/gitlab/shared
              readOnly: false
+           - name: shared-tmp
+             mountPath: '/tmp'
+             readOnly: false
{{ include "gitlab.certificates.volumeMount" . | indent 12 }}
{{ include "gitlab.extraVolumeMounts" . | indent 12 }}
-         livenessProbe:
-           exec:
-             command:
-             - /scripts/healthcheck
-           initialDelaySeconds: 20
-           timeoutSeconds: 30
-           periodSeconds: 60
-         readinessProbe:
-           exec:
-             command:
-             - /scripts/healthcheck
-           timeoutSeconds: 2
          resources:
{{ toYaml .Values.workhorse.resources | indent 12 }}
      volumes:
{{ include "gitlab.extraVolumes" . | indent 6 }}
{{ include "gitlab.psql.ssl.volume" . | indent 6 }}
{{- if .Values.metrics.enabled }}
      - name: unicorn-metrics
        emptyDir:
          medium: "Memory"
{{- end }}
      - name: unicorn-config
        configMap:
          name: {{ template "fullname" . }}
      - name: workhorse-config
        configMap:
            name: {{ .Release.Name }}-workhorse-config
      - name: init-unicorn-secrets
        projected:
          defaultMode: 0400
          sources:
          - secret:
              name: {{ template "gitlab.rails-secrets.secret" . }}
              items:
                - key: secrets.yml
                  path: rails-secrets/secrets.yml
          - secret:
              name: {{ template "gitlab.gitlab-shell.authToken.secret" . }}
              items:
                - key: {{ template "gitlab.gitlab-shell.authToken.key" . }}
                  path: shell/.gitlab_shell_secret
          - secret:
              name: {{ template "gitlab.gitaly.authToken.secret" . }}
              items:
                - key: {{ template "gitlab.gitaly.authToken.key" . }}
                  path: gitaly/gitaly_token
          {{- if .Values.global.redis.password.enabled }}
          - secret:
              name: {{ template "gitlab.redis.password.secret" . }}
              items:
                - key: {{ template "gitlab.redis.password.key" . }}
                  path: redis/password
          {{- end }}
          - secret:
              name: {{ template "gitlab.psql.password.secret" . }}
              items:
                - key: {{ template "gitlab.psql.password.key" . }}
                  path: postgres/psql-password
          - secret:
              name: {{ template "gitlab.registry.certificate.secret" . }}
              items:
                - key: registry-auth.key
                  path: registry/gitlab-registry.key
          - secret:
              name: {{ template "gitlab.workhorse.secret" . }}
              items:
                - key: {{ template "gitlab.workhorse.key" . }}
                  path: gitlab-workhorse/secret
          {{- include "gitlab.minio.mountSecrets" $ | nindent 10 }}
          {{- include "gitlab.appConfig.objectStorage.mountSecrets" (dict "name" "artifacts" "config" $.Values.global.appConfig.artifacts) | nindent 10 }}
          {{- include "gitlab.appConfig.objectStorage.mountSecrets" (dict "name" "lfs" "config" $.Values.global.appConfig.lfs) | nindent 10 }}
          {{- include "gitlab.appConfig.objectStorage.mountSecrets" (dict "name" "uploads" "config" $.Values.global.appConfig.uploads) | nindent 10 }}
          {{- include "gitlab.appConfig.objectStorage.mountSecrets" (dict "name" "packages" "config" $.Values.global.appConfig.packages) | nindent 10 }}
          {{- include "gitlab.appConfig.objectStorage.mountSecrets" (dict "name" "external_diffs" "config" $.Values.global.appConfig.externalDiffs) | nindent 10 }}
          {{- include "gitlab.appConfig.ldap.servers.mountSecrets" $ | nindent 10 }}
          {{- include "gitlab.appConfig.omniauth.mountSecrets" $ | nindent 10 }}
          {{- if and $.Values.global.smtp.enabled $.Values.global.smtp.authentication }}
          - secret:
              name: {{ .Values.global.smtp.password.secret | required "Missing required secret containing the SMTP password. Make sure to set `global.smtp.password.secret`" }}
              items:
                - key: {{ .Values.global.smtp.password.key }}
                  path: smtp/smtp-password
          {{- end }}
      - name: unicorn-secrets
        emptyDir:
          medium: "Memory"
      - name: workhorse-secrets
        emptyDir:
          medium: "Memory"
      - name: shared-upload-directory
        emptyDir: {}
      - name: shared-artifact-directory
        emptyDir: {}
+     - name: shared-tmp
+       emptyDir: {}
{{ include "gitlab.certificates.volumes" . | indent 6 }}
    {{- if .Values.nodeSelector }}
      nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
    {{- end }}
{{- end }}

sidekiqのConfigMapにも同様に相対パスの設定を入れます。

gitlab/charts/gitlab/charts/sidekiq/templates/configmap.yaml
{{- if .Values.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "fullname" . }}
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
data:
  installation_type: |
    gitlab-helm-chart
  database.yml.erb: |
    production:
      adapter: postgresql
      encoding: unicode
      database: {{ template "gitlab.psql.database" . }}
      pool: 10
      username: {{ template "gitlab.psql.username" . }}
      password: "<%= File.read("/etc/gitlab/postgres/psql-password").strip.dump[1..-2] %>"
      host: {{ template "gitlab.psql.host" . }}
      port: {{ template "gitlab.psql.port" . }}
      prepared_statements: {{ template "gitlab.psql.preparedStatements" . }}
      # load_balancing:
      #   hosts:
      #     - host1.example.com
      #     - host2.example.com
{{- include "gitlab.psql.ssl.config" . | indent 6 }}
  smtp_settings.rb: |
{{ include "gitlab.smtp_settings" . | indent 4 }}
  resque.yml.erb: |
    production:
      # Redis (single instance)
      url: {{ template "gitlab.redis.url" . }}
      id:
  gitlab.yml.erb: |
    production: &base
      gitlab:
        host: {{ template "gitlab.gitlab.hostname" . }}
        https: {{ hasPrefix "https://" (include "gitlab.gitlab.url" .) }}
+       relative_url_root: /gitlab
        {{- with .Values.global.hosts.ssh }}
        ssh_host: {{ . | quote }}
        {{- end }}
        {{- with .Values.global.appConfig }}
        impersonation_enabled: {{ .enableImpersonation }}
        usage_ping_enabled: {{ eq .enableUsagePing true }}
        default_can_create_group: {{ eq .defaultCanCreateGroup true }}
        username_changing_enabled: {{ eq .usernameChangingEnabled true }}
        issue_closing_pattern: {{ .issueClosingPattern | quote }}
        default_theme: {{ .defaultTheme }}
        {{- include "gitlab.appConfig.defaultProjectsFeatures.configuration" $ | nindent 8 }}
        webhook_timeout: {{ .webhookTimeout }}
        {{- end }}
        trusted_proxies:
        {{- if .Values.trusted_proxies }}
{{ toYaml .Values.trusted_proxies | indent 10 }}
        {{- end }}
        time_zone: {{ .Values.global.time_zone | quote }}
        email_from: {{ template "gitlab.email.from" . }}
        email_display_name: {{ .Values.global.email.display_name | quote }}
        email_reply_to: {{ template "gitlab.email.reply_to" . }}
        email_subject_suffix: {{ .Values.global.email.subject_suffix | quote }}
      {{- with .Values.global.appConfig }}
      {{- if eq .incomingEmail.enabled true }}
{{ include "gitlab.appConfig.incoming_email" . | indent 6 }}
      {{- end }}
      gravatar:
        plain_url: {{ .gravatar.plainUrl }}
        ssl_url: {{ .gravatar.sslUrl }}
      {{- include "gitlab.appConfig.cronJobs" . | nindent 6 }}
{{ include "gitlab.appConfig.extra" . | indent 6 }}
      {{- end }}
      {{- include "gitlab.appConfig.artifacts.configuration" (dict "config" $.Values.global.appConfig.artifacts "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.lfs.configuration" (dict "config" $.Values.global.appConfig.lfs "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.uploads.configuration" (dict "config" $.Values.global.appConfig.uploads "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.packages.configuration" (dict "config" $.Values.global.appConfig.packages "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.external_diffs.configuration" (dict "config" $.Values.global.appConfig.externalDiffs "context" $) | nindent 6 }}
      {{- include "gitlab.appConfig.pseudonymizer.configuration" $ | nindent 6 }}
      pages:
        enabled: false
      mattermost:
        enabled: false
      ## Registry Integration
      {{- include "gitlab.appConfig.registry.configuration" $ | nindent 6 }}
      gitlab_ci:
      {{- include "gitlab.appConfig.ldap.configuration" $ | nindent 6 }}
      {{- include "gitlab.appConfig.omniauth.configuration" $ | nindent 6 }}
      kerberos:
        enabled: false
      shared:
{{ include "gitlab.appConfig.gitaly" . | indent 6 }}
{{ include "gitlab.appConfig.repositories" . | indent 6 }}
      backup:
        path: "tmp/backups"   # Relative paths are relative to Rails.root (default: tmp/backups/)
{{ include "gitlab.appConfig.shell" . | indent 6 }}
      workhorse:
      git:
        bin_path: /usr/bin/git
      webpack:
      monitoring:
        ip_whitelist:
          - 127.0.0.0/8
        sidekiq_exporter:
{{- if .Values.metrics.enabled }}
          enabled: true
          address: 0.0.0.0
          port: {{ .Values.metrics.port }}
{{- end }}
  configure: |
    {{- include "gitlab.scripts.configure.secrets" (dict "required" "gitaly registry postgres rails-secrets") | nindent 4 -}}
    {{- include "gitlab.psql.ssl.initScript" . | nindent 4 }}
# Leave this here - This line denotes end of block to the parser.
{{- end }}

gitalyのConfigMapにも相対パスの設定を入れます。

gitlab/charts/gitlab/charts/gitaly/templates/configmap.yaml
{{- if .Values.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "fullname" . }}
  namespace: {{ $.Release.Namespace }}
  labels:
{{ include "gitlab.standardLabels" . | indent 4 }}
data:
  configure: |
    set -e
    mkdir -p /init-secrets/gitaly /init-secrets/shell
    cp -v -r -L /init-config/.gitlab_shell_secret  /init-secrets/shell/.gitlab_shell_secret
    cp -v -r -L /init-config/gitaly_token  /init-secrets/gitaly/gitaly_token
    {{- if .Values.global.redis.password.enabled }}
    mkdir -p /init-secrets/redis
    cp -v -r -L /init-config/redis_password  /init-secrets/redis/redis_password
    {{- end }}
  config.toml.erb: |
    # The directory where Gitaly's executables are stored
    bin_dir = "/usr/local/bin"

    # listen on a TCP socket. This is insecure (no authentication)
    listen_addr = "0.0.0.0:8075"

    # If metrics collection is enabled, inform gitaly about that
    {{- if .Values.metrics.enabled }}
    prometheus_listen_addr = "localhost:{{ .Values.metrics.metricsPort }}"
    {{- end }}

    <% @storages = [ {{- range (coalesce .Values.internal.names .Values.global.gitaly.internal.names) }} {{ . | quote }}, {{- end }} ] %>
    <% @index=`echo ${HOSTNAME##*-}`.to_i %>
    <% if @storages.length > @index %>
    [[storage]]
    name = "<%= @storages[@index] %>"
    path = "/home/git/repositories"
    <% else %>
    <% raise Exception, "Storage for node #{@index} is not present in the storageNames array. Did you use kubectl to scale up ? You need to solely use helm for this purpose" %>
    <% end %>

    [logging]
    {{- with .Values.logging }}
    {{- if .level }}
    level = "{{ .level }}"
    {{- end }}
    {{- if .format }}
    format = "{{ .format }}"
    {{- end }}
    {{- if .sentryDsn }}
    sentry_dsn = "{{ .sentryDsn }}"
    {{- end }}
    {{- if .rubySentryDsn }}
    ruby_sentry_dsn = "{{ .rubySentryDsn }}"
    {{- end }}
    {{- if .sentryEnvironment }}
    sentry_environment = "{{ .sentryEnvironment }}"
    {{- end }}
    {{- end }}

    {{- if .Values.prometheus.grpcLatencyBuckets }}
    [prometheus]
    grpc_latency_buckets = {{ .Values.prometheus.grpcLatencyBuckets }}
    {{- end }}

    [auth]
    token = "<%= File.read('/etc/gitlab-secrets/gitaly/gitaly_token').strip.dump[1..-2] %>"

    [git]
    {{- with .Values.git }}
    {{- if .catFileCacheSize }}
    catfile_cache_size = {{ .catFileCacheSize }}
    {{- end }}
    {{- end }}

    [gitaly-ruby]
    # The directory where gitaly-ruby is installed
    dir = "/srv/gitaly-ruby"
    {{- with .Values.ruby }}
    {{- if .maxRss }}
    max_rss = {{ .maxRss }}
    {{- end }}
    {{- if .gracefulRestartTimeout }}
    graceful_restart_timeout = "{{ .gracefulRestartTimeout }}"
    {{- end }}
    {{- if .restartDelay }}
    restart_delay = "{{ .restartDelay }}"
    {{- end }}
    {{- if .numWorkers }}
    num_workers = {{ .numWorkers }}
    {{- end }}
    {{- end }}

    [gitlab-shell]
    # The directory where gitlab-shell is installed
    dir = "/srv/gitlab-shell"

    {{- if .Values.shell.concurrency }}
    {{- range .Values.shell.concurrency }}
    {{- if and .rpc .maxPerRepo }}
    [[concurrency]]
    rpc = "{{ .rpc }}"
    max_per_repo = {{ .maxPerRepo }}
    {{- end }}
    {{- end }}
    {{- end }}

  shell-config.yml.erb: |
    # GitLab user. git by default
    user: git

    # Url to gitlab instance. Used for api calls. Should end with a slash.
-   gitlab_url: "http://{{ template "gitlab.unicorn.host" . }}:{{ default 8080 .Values.unicorn.port }}/"
+   gitlab_url: "http://{{ template "gitlab.unicorn.host" . }}:{{ default 8080 .Values.unicorn.port }}/gitlab/"

    secret_file: /etc/gitlab-secrets/shell/.gitlab_shell_secret

    http_settings:
      self_signed_cert: false

    # File used as authorized_keys for gitlab user
    auth_file: "/home/git/.ssh/authorized_keys"

    # Redis settings used for pushing commit notices to gitlab
    redis:
      host: {{ template "gitlab.redis.host" . }}
      port: {{ template "gitlab.redis.port" . }}
      {{- if .Values.global.redis.password.enabled }}
      pass: "<%= File.read("/etc/gitlab-secrets/redis/redis_password").strip.dump[1..-2] %>"
      {{- end }}
      database: nil
      namespace: resque:gitlab

    # Log file.
    # Default is gitlab-shell.log in the root directory.
    log_file: "/var/log/gitaly/gitlab-shell.log"

    # Log level. INFO by default
    log_level: INFO

    # Audit usernames.
    # Set to true to see real usernames in the logs instead of key ids, which is easier to follow, but
    # incurs an extra API call on every gitlab-shell command.
    audit_usernames: false
# Leave this here - This line denotes end of block to the parser.
{{- end }}

values

XXXXXXXXXX.ap-northeast-1.elb.amazonaws.comはALBのエンドポイント、YYYYYYYYYY.ap-northeast-1.rds.amazonaws.comはRDSのエンドポイントとなります。
IngressはNGINX Ingress Controllerに読み込ませます。
IAM Roleは事前に作成したものです。

gitlab/my-values.yaml
global:
  edition: ce
  hosts:
    https: false
    gitlab:
      name: XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com
      https: false
  ingress:
    configureCertmanager: false
    enabled: false
    tls:
      enabled: false
  psql:
    password: 
      secret: gitlab-postgresql
      key: password
    host: YYYYYYYYYY.ap-northeast-1.rds.amazonaws.com
    port: 5432
    username: gitlab
    database: gitlabhq_production
  minio:
    enabled: false
  appConfig:
    lfs:
      bucket: test-gitlab-lfs
      connection:
        secret: gitlab-rails-storage
        key: connection
    artifacts:
      bucket: test-gitlab-artifacts
      connection:
        secret: gitlab-rails-storage
        key: connection
    uploads:
      bucket: test-gitlab-uploads
      connection:
        secret: gitlab-rails-storage
        key: connection
    packages:
      bucket: test-gitlab-packages
      connection:
        secret: gitlab-rails-storage
        key: connection
    externalDiffs:
      enabled: true
      bucket: test-gitlab-mr-diffs
      connection:
        secret: gitlab-rails-storage
        key: connection
    pseudonymizer:
      configMap:
      bucket: test-gitlab-pseudo
      connection:
        secret: gitlab-rails-storage
        key: connection
    backups:
      bucket: test-gitlab-backups
      tmpBucket: test-gitlab-tmp
  time_zone: Tokyo
upgradeCheck:
  enabled: false
certmanager:
  install: false
nginx-ingress:
  enabled: false
prometheus:
  install: false
postgresql:
  install: false
gitlab-runner:
  install: false
registry:
  minReplicas: 1
  storage:
    secret: registry-storage
    key: config
  annotations:
    iam.amazonaws.com/role: s3-full-access-role
  ingress:
    enabled: false
gitlab:
  gitlab-shell:
    enabled: false
  gitlab-exporter:
    enabled: false
  unicorn:
    annotations:
      iam.amazonaws.com/role: s3-full-access-role
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
  sidekiq:
    annotations:
      iam.amazonaws.com/role: s3-full-access-role
  task-runner:
    enabled: false

GitLabのデプロイ

次のコマンドでGitLabをデプロイします。

$ helm install ¥
  --name test-gitlab ¥
  --namespace test ¥
  --values ./gitlab/my-values.yaml ¥
  ./gitlab 

確認

/gitlabにアクセスして問題ないことが確認できます。

スクリーンショット 2020-09-21 18.29.22.png

GitLab RunnerをデプロイしておけばGitLab CIを実行することも可能です。

スクリーンショット 2020-09-21 18.31.51.png

まとめ

EKSにHelmでGitLabをデプロイするときでも相対URLを使用できることを確認しました。
assetsに関してはNGINX Ingress Controllerのrewrite targetで回避せずとも解決できる方法を模索しましたが、すぐに対処することが厳しそうでした。
今回の対応ではtemplatesの中身を変更することになるので、バージョンアップの際には都度対応が必要ということに注意してください。

参考

https://gitlab.com/gitlab-org/charts/gitlab/-/issues/406
https://docs.gitlab.com/ee/install/relative_url.html
https://gitlab.com/gitlab-org/charts/gitlab/-/issues/1647

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

AWS CDKを使ってlambda layerをデプロイしてみた

はじめに

自己研鑽の一環でAWS CDKを使っているのですが、lambda layerをデプロイするようなサンプルがあまりないように感じたので、自分で試行錯誤しながらやってみました。

開発環境

  • aws-cdk:1.58.0
  • cdk使用言語:typescript

前提として

cdkを使えるような環境のセットアップが出来ている前提で記載していきます。
私は以下の記事を参考にさせていただきました。
https://dev.classmethod.jp/articles/aws-cdk-101-typescript/

ディレクトリ構成

本記事のディレクトリ構成は以下のようになっています。

├─backend
│   └─ src
│     ├─function
│     │  └─sample
│     └─layer
│      └─SharedLayer
│        └─nodejs
│         └─node_modules
└─cdk-stack
     ├─bin
     ├─lib
     └─test

定義してみる

さっそくですがlambda layerを定義していきます。

cdk-stack/lib/cdk-stack.ts
// lambdaとlambda layerを定義するためのライブラリ
import { AssetCode, Function, Runtime, LayerVersion } from '@aws-cdk/aws-lambda';

// lambdaLayerの定義
const nodeModulesLayer = new LayerVersion(this, 'NodeModulesLayer',
  {
    code: AssetCode.fromAsset('../backend/src/layer/SharedLayer'),
    compatibleRuntimes: [Runtime.NODEJS_12_X]
  }
);

以上です。
たったこれだけで定義が出来るのはすごいですね。

ポイントはbackendのディレクトリ構成です。
lambda layerはnodejs/node_modulesのディレクトリ構成にする必要があり、cdk-stack.tsではcodeにnodejs以下を含むディレクトリを指定しています。

lambdaに適用してみる

先ほど定義したlayerをlambdaに適用してみます。

cdk-stack/lib/cdk-stack.ts
// lambda関数の定義
const sampleLambda = new Function(this, `sample-lambda-function`, {
  code: new AssetCode('../backend/src/function/sample'),
  handler: 'index.handler',
  runtime: Runtime.NODEJS_12_X,
  layers: [nodeModulesLayer],
  functionName: 'sampleFunction'
});

上記のようにlayersのキーに配列で定義したlambda layerを記載してあげるだけでOKです。
あとはcdk deployコマンドでデプロイすれば完了です。

さいごに

やはりプログラミングをする感覚でサーバレスアーキテクチャを構築できるのはyamlで書くより直感的で良いですね。
今後は実務での利用を目指してAPIGatewayのリソースポリシーやIAMロールなどのセキュリティ周りを正しく設定できるように色々調べたいと思います。

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

Amazon Route 53

Amazon Route53とは

可用性と拡張性に優れた、ドメイン管理機能と権威DNSを持つサービスです。
DNS情報を参照するだけではなく、ネットワークトラフィックのルーティングや接続先の状況に応じた接続先の変更などが可能です。

ちなみになぜ53かと言うとDNSが使用するポート番号が53番だからだそうです。

ドメイン管理

Route53では新規ドメインの取得や更新などの行うことが出来ます。
◯メリット
・ドメイン取得からドメイン登録設定がとても簡単
・ドメイン使用料が通常のAWS利用料の請求に含まれる一貫管理が可能

△デメリット
・有料
・ドメインの維持コストや取得コスト自体を比較するとお名前.com等の方が安くすむ事が多い。

ホストゾーン

管理可能なレコード情報の集合
レコード情報とは192.168.100.100example.comですよ〜といったドメインをIPアドレスに変換する為の情報

◯パブリックホストゾーンとプライベートホストゾーン

■パブリックホストゾーン
インターネット上に公開されたDNSにメインレコードを管理する。
example.comとそのサブドメイン(hoge.example.comfuga.example.com)のトラフィックをインターネット でルーティングする方法についての情報を保持するコンテナ。

■プライベートホストゾーン
VPCで構成されたプライベートネットワークにおいてDNSドメインのレコードを管理する。(VPC内のサーバー間で名前解決したい時とか)

レコードタイプ

◯Aliasレコード

Route53の特徴的なレコードとしてAliasレコードがあり、レコード情報に登録する値として、CloudFront, ELB, S3などのAWSリソースFQDNを指定できる。
CNAMEでも同じような登録は可能ですが、大きな違いとして最上位ドメインのZoneApexにも登録出来るということが挙げられます。
DNSクエリ回数が減りパフォーマンス向上が可能です。

タイプ 説明 機能
A IPv4 アドレス hoge.example.com54.13.123.456サーバーへ
AAAA IPv6 アドレス 上記のIPv6版
CNAME 正規名 fuga.example.comはAレコードのhoge.example.com
MX メール交換 受信用のメールサーバを指定するためのレコード。メールサーバーが複数ある場合、プリファレンス値という数値が小さいものから優先される。これ→10 メールサーバ.com
NS ネームサーバー ドメインのネームサーバを指定しているレコード
SOA 管理情報の始点 権威DNSが管理する範囲に関する情報
TXT テキスト 送信許可するメールサーバを指定できる。(SPF設定)

※ざっくり説明です。
他にもいくつかありますので気になる方はこちら見て下さい。

ルーティングポリシー

◯シンプルルーティングポリシー

標準のDNSレコードで設定。1つのウェブサイトにルーティング。DNSレコード1つに複数のIPを設定可能。静的マッピング。

◯フェイルオーバールーティングポリシー

アクティブ/スタンバイ方式で、アクティブのリソースが正常な場合にルーティングし、正常でない場合はスタンバイ側のリソースにルーティング。システム障害時にSorryサーバ−を表示することが可能。

◯位置情報ルーティングポリシー

ユーザーの位置情報に基づいてトラフィックをルーティングする際に使用する。
アクセス位置情報に基づいて応答するサーバーを変化することができる為、日本からのアクセスは日本語、アメリカからのアクセスは英語といった制御が可能。

◯地理的接近性ルーティングポリシー

リソースの場所に基づいてトラフィックをルーティングし、必要に応じてトラフィックをある場所のリソースから別の場所のリソースに移動、特定のリソースにルーティングするトラフィックの量の変更にはバイアスという値を指定する。
トラフィックフローを使用する必要がある。

◯レイテンシールーティングポリシー

複数箇所にサーバーが分散されている場合に、レイテンシー(遅延)が最も少ないサーバーにリクエストをルーティングする。特定のサーバーだけ高負荷になった場合にリクエストを分散することが出来る。

◯複数値回答ルーティングポリシー

複数値回答ルーティングは、ヘルスチェックでNGになったIPアドレスは返却されずに正常なサーバーにのみアクセスするることが出来る。ヘルスチェックがNGになったIPアドレスは返却されずステータスが異常となる。

◯加重ルーティングポリシー

重み付けを設定でき重み付けの高いリソースに、より多くルーティングする。
A/Bテスト、段階的な以降、サーバーの性能調整などに利用される。

トラフィックフロー

各レコード間の設定が複雑になることがあり、トラフィックフローは各レコードの組み合わせをビジュアル的にわかりやすく組み合わせる為のツールを使って定義できる。

【参考】

What is Amazon Route 53?
DNSとは? 全体像とDNSレコードの種類
AWS認定ソリューションアーキテクト[アソシエイト]
VPC内での名前解決を有効にする Route53 のプライベートホストゾーンを使ってみた

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

AWS SSOにAPIが追加されてCLIやCloudFormationで操作できるようになったよ

はじめに

2020/9/10 ついに AWS Single Sign-On に AWS SSO API (sso-admin) が追加され、
AWS CLI/SDK や CloudFormation による操作もサポートされました。

:rocket: AWS Single Sign-On adds account assignment APIs and AWS CloudFormation support to automate multi-account access management
https://aws.amazon.com/jp/about-aws/whats-new/2020/09/aws-single-sign-on-adds-account-assignment-apis-and-aws-cloudformation-support-to-automate-multi-account-access-management/

これまで 権限セットや AWS アカウントへのユーザー/グループの割り当てといった
AWS SSO の管理操作は API が提供されておらず、コンソールで手動設定するしかありませんでした。
今回のアップデートにより、アカウント割り当て設定の自動化や IaC への道がひらけました!!

CloudFormation のサポート

2020年9月時点で CloiudFormation でサポートされているリソースは以下の2つです

  • AWS::SSO::PermissionSet
    AWS SSO インスタンスに設定する権限を指定します。

  • AWS::SSO::Assignment
    指定された権限セットを使用して、AWS アカウントへのアクセスを割り当てます。

注意点

CloudFormation で AWS SSOリソースを作成するには、InstanceArn, Identiy-Store-Id,
UserId, GroupId などのさまざまな リソース ID が必要です。
これらの ID は AWS SSO コンソールに表示されないため、API を介して取得する必要があります。

ただし PermissionSetArn や UserId/GroupId は各詳細ページの URL にも含まれていて
ps-xxxxxxxxxxxxxxxx といったリソースID や GUID を確認することもできます。
image.png
image.png

以降では AWS CLI で 各リソース ID を取得する方法も合わせて紹介します。
AWS SSO API (sso-admin) を使用するには、Version 1.18.136 または 2.0.48 以降が必要です。

権限セットの指定例

AWS 管理ポリシーを使用したシンプルな権限セットを作成する例です。

PermissionSet:
  Type: AWS::SSO::PermissionSet
  Properties:
    InstanceArn: 'arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx'
    Name: 'AdministratorAccess'
    ManagedPolicies:
    - 'arn:aws:iam::aws:policy/AdministratorAccess'

InstanceArn には SSO インスタンスの ARN を指定します。
sso-admin の list-instances から取得します。

$ aws sso-admin list-instances
{
    "Instances": [
        {
            "InstanceArn": "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx",
            "IdentityStoreId": "d-xxxxxxxxxx"
        }
    ]
}

アカウント割り当ての指定例

以下のように AWS アカウント、権限セット、プリンシパルを指定します。

Assignment: 
  Type: AWS::SSO::Assignment
  Properties: 
    InstanceArn: 'arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx'
    PermissionSetArn: 'arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxxx'
    TargetId: '123456789012'
    TargetType: AWS_ACCOUNT
    PrincipalId: 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6'
    PrincipalType: 'GROUP'

PermissionSetArn は sso-admin の describe-permission-set で確認します。

$ aws sso-admin list-permission-sets \
> --instance-arn arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx 
{
    "PermissionSets": [
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxxx",
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-yyyyyyyyyyyyyyyy",
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-zzzzzzzzzzzzzzzz"
    ]
}

$ aws sso-admin describe-permission-set \
> --instance-arn arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxx \
> --permission-set-arn arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxx
{
    "PermissionSet": {
        "Name": "AdministratorAccess",
        "PermissionSetArn": "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxx",
        "CreatedDate": "2020-09-09T19:01:06.758000+09:00",
        "SessionDuration": "PT1H"
    }
}

CloudFormation で権限セットを作成しているのであれば
組み込み関数の Fn::GetAtt で ARN を参照するのが一番簡単です。

    PermissionSetArn: !GetAtt LogicalId.PermissionSetArn

TargetType は AWS_ACCOUNT のみが指定可能で
TargetId には割り当てを行う AWS アカウントIDを指定します。

    TargetId: '123456789012'
    TargetType: AWS_ACCOUNT

PrincipalType には USER または GROUP を指定できます。
PrincipalId には割り当てを行うユーザー/グループの GUID を指定する必要があります。

    PrincipalId: 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6'
    PrincipalType: 'GROUP'

AWS SSO API (sso-admin) の多くのオペレーションは、プリンシパルと呼ばれる
ユーザーとグループの識別子に依存しています。
ユーザー/グループの GUID を取得するには AWS SSO Identity Store API (identitystore) の
list-users, list-groups を使用します。
--identity-store-id には 先ほど sso-admin の list-instances で確認した
ID ストアの識別子 (d-xxxxxxxxxx) を指定する必要があります。

list-users では UserName、list-group では DisplayName をフィルターに指定することができます。

$ aws identitystore list-users \
> --identity-store-id d-xxxxxxxxxx \
> --filters AttributePath=UserName,AttributeValue="user@example.com"
{
    "Users": [
        {
            "UserName": "userXX@examle.com",
            "UserId": "f81d4faxge-7dec11d8-a765-3at5-80e4-00a0c91e6bf6"
        }
    ]
}

$ aws identitystore list-groups \
> --identity-store-id d-xxxxxxxxxx \
> --filters AttributePath=DisplayName,AttributeValue="テストグループ"
{
    "Groups": [
        {
            "GroupId": "f81d4faxge-789fcfa5-005c-4379-89ba-10a11e641c17"
            "DisplayName": "テストグループ"
        }
    ]
}

テンプレート例

上記を踏まえて作成した、権限セットおよびそれを使用したアカウント割り当てを行う
CloudFormation のテンプレート例です。
ユーザーやグループの GUID やアカウント ID については複数の割り当てが発生するため
マッピングを使用して値を参照するようにしてみましたが、もっとよいやり方がありそうな気がします。

AWSTemplateFormatVersion: "2010-09-09"
Description: Configure AWS SSO Account Assignment

Parameters: 
  pAwsSsoInsanceArn: 
    Type: String
    Default: 'arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxx'
    Description: 'No change required.'

Mappings: 
  mUsers:
    User1:
      Id: 'f81d4faxge-7dec11d8-a765-3at5-80e4-00a0c91e6bf6'
  mGroups:
    Group1:
      Id: 'f81d4faxge-60b1e9ac-edda-4c9a-a4fd-5f26197afb79'
    Group2:
      Id: 'f81d4faxge-3a2f9d97-8e37-4e3d-9d46-3cf701a00296'
  mAccounts:
    Account1:
      Id: '111111111111'
    Account2:
      Id: '222222222222'
    Account3:
      Id: '333333333333'

Resources:
# Permission Set
  rPermissionSetAdministratorAccess:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: !Ref pAwsSsoInsanceArn
      Name: 'AdministratorAccess'
      ManagedPolicies:
        - 'arn:aws:iam::aws:policy/AdministratorAccess'

  rPermissionSetSecurityAudit:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: !Ref pAwsSsoInsanceArn
      Name: 'SecurityAudit'
      ManagedPolicies:
        - 'arn:aws:iam::aws:policy/SecurityAudit'

  rPermissionSetViewOnlyAccess:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: !Ref pAwsSsoInsanceArn
      Name: 'ViewOnlyAccess'
      ManagedPolicies:
        - 'arn:aws:iam::aws:policy/job-function/ViewOnlyAccess'

# Account Assignment
  rAccoiunt1Group1AdministratorAccess:
    Type: AWS::SSO::Assignment
    Properties: 
      InstanceArn: !Ref pAwsSsoInsanceArn
      PermissionSetArn: !GetAtt rPermissionSetAdministratorAccess.PermissionSetArn
      PrincipalId: !FindInMap [ mGroups, Group1, Id ]
      PrincipalType: 'GROUP'
      TargetId: !FindInMap [ mAccounts, Account1, Id ]
      TargetType: 'AWS_ACCOUNT'

  rAccount2Group1ViewOnlyAccess:
    Type: AWS::SSO::Assignment
    Properties: 
      InstanceArn: !Ref pAwsSsoInsanceArn
      PermissionSetArn: !GetAtt rPermissionSetViewOnlyAccess.PermissionSetArn
      PrincipalId: !FindInMap [ mGroups, Group1, Id ]
      PrincipalType: 'GROUP'
      TargetId: !FindInMap [ mAccounts, Account2, Id ]
      TargetType: 'AWS_ACCOUNT'

  rAccount3User1AdministratorAccess:
    Type: AWS::SSO::Assignment
    Properties: 
      InstanceArn: !Ref pAwsSsoInsanceArn
      PermissionSetArn: !GetAtt rPermissionSetAdministratorAccess.PermissionSetArn
      PrincipalId: !FindInMap [ mUsers, User1, Id ]
      PrincipalType: 'USER'
      TargetId: !FindInMap [ mAccounts, Account3, Id ]
      TargetType: 'AWS_ACCOUNT'

  rAccount1Group2SecurityAudit:
    Type: AWS::SSO::Assignment
    Properties: 
      InstanceArn: !Ref pAwsSsoInsanceArn
      PermissionSetArn: !GetAtt rPermissionSetSecurityAudit.PermissionSetArn
      PrincipalId: !FindInMap [ mGroups, Group2, Id ]
      PrincipalType: 'GROUP'
      TargetId: !FindInMap [ mAccounts, Account1, Id ]
      TargetType: 'AWS_ACCOUNT'

まとめ

  • AWS SSO API (sso-admin) が追加され、管理作業の一部自動化や IaC が可能になった
  • CloudFormation では権限セットの作成とアカウント割り当てをサポート
  • プリンシパルの指定に必要なユーザー/グループの ID は IdentityStore API で取得する必要がある
    • アカウント割り当てを行う際に ID の検索が必要なため今のところ完全自動化は難易度が高そう
    • AWS SSO のコンソールではこれらの ID や ARN が表示されないため少々不便
  • とはいえ、AWS SSO を API で管理できるのはとてもエキサイティング!

参考

AWS CloudFormation User Guide - SSO resource type reference
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_SSO.html
AWS CLI Command Reference - sso-admin
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sso-admin/index.html
AWS CLI Command Reference - identitystore
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/identitystore/index.html

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

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

【Bitcoinを自動売買】AWSのDockerで運用してみた話、外出先のAppleWatchから1TAPでON/OFF&Line通知付き

autocoin2

BitCoinのFX自動売買プログラム。
BitflyerのAPIを利用して、node.jsにて仮想通貨トレードを自動化しました。
寝てる時、トイレ中、24時間中、お金が勝手に増えてくれたら、どんなに素敵だろう。。:gem:
楽して自動的に儲かりたい!そんなダメ人間モチベーションで作ってみました。
iOS の画像.jpgスクリーンショット 2020-09-20 10.05.38.png

いきなり結論ですが、、、残念ながら儲かりません:scream:
むしろ、減っています。。

ですが、チューニングしたら、ひょっとしたら儲かり出すかもしれません。
(損害を受けても当方は一切責任はありません。)
あくまで、自己責任でお願いします!

Githubにコード公開しています

特徴

  • 売り・買いポジション両方対応
  • 複数アルゴリズムによる重み付け売買判断
  • MongoDBによる売買履歴の保存
  • 取引開始をLine通知
  • 損得金額の閾値を超えたら、Lineにて通知
  • 一定の日数が経過したら、ポジションを自動で手放す機能
  • 日付変更30分前には、新たなポジション取得を抑制する機能
  • Apple Home連携で外出先でもiphoneから1タップでON/OFF
  • プログラム稼働中でも、並行して通常の人的トレードも可能

システム概要

autocoin2.png

使用技術

  • Node.js
  • Docker
  • AWS
  • MongoDB
  • shell
  • Raspberry Pi

ディレクトリ構成

.
├── autocoin
│  ├── algo.js
│  ├── app.js
│  ├── config.js
│  ├── crypto.js
│  ├── line.js
│  ├── mongo.js
│  └── utils.js
├── container_data
├── homebridge_AWS
│  ├── startAWS.sh
│  └── stopAWS.sh
├── .env
├── Dockerfile
└── docker-compose.yml

メイン:app.js

このプログラムのエントリーポイント。
ループ処理でコードを廻すことで売買を繰り返します。

'use strict';
const config = require('./config');
const moment = require('moment');
const ccxt = require('ccxt');
const bitflyer = new ccxt.bitflyer(config);

const Crypto = require('./crypto')
const Mongo = require('./mongo');
const mongo = new Mongo();
const Line = require('./line');
const line = new Line(config.line_token)
const utils = require('./utils');
const Algo = require('./algo');

//取引間隔(秒)
const tradeInterval = 180;
//取引量
const orderSize = 0.01;
//swap日数
const swapDays = 3;
//通知用の価格差閾値
const infoThreshold = 100;

//psychoAlgoの設定値;陽線カウント
const psychoParam = {
  'range': 10,
  'ratio': 0.7,
};
//crossAlgoの設定値:移動平均幅
const crossParam = {
  'shortMA': 5,
  'longMA': 30,
};

//ボリンジャーバンド設定値
const BBOrder = {
  //注文
  'period': 10,
  'sigma': 1.7
};
const BBProfit = {
  //利確
  'period': 10,
  'sigma': 1
};
const BBLossCut = {
  //損切り:日足で判断
  'period': 10,
  'sigma': 2.5
};

// アルゴリズムの重み付け:未使用は0にする
const algoWeight = {
  // 'psychoAlgo': 0,
  // 'crossAlgo': 0,
  // 'bollingerAlgo': 1,
  'psychoAlgo': 0.1,
  'crossAlgo': 0.2,
  'bollingerAlgo': 0.7,
};
//取引判断の閾値
const algoThreshold = 0.3;
//ロスカットの閾値
const lossCutThreshold = 0.5;


(async function () {
  let sumProfit = 0;
  let beforeProfit = null;
  const nowTime = moment();
  const collateral = await bitflyer.fetch2('getcollateral', 'private', 'GET');

  //(分)レコード作成
  const crypto = new Crypto();
  const beforeHour = crossParam.longMA * tradeInterval;
  const timeStamp = nowTime.unix() - beforeHour;
  let records = await crypto.getOhlc(tradeInterval, timeStamp);

  const algo = new Algo(records);

  //Lineに自動売買スタートを通知
  const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');
  const message = `\n 自動売買スタート\n date: ${strTime}\n collateral: ${collateral.collateral}`;
  line.notify(message);


  while (true) {
    let flag = null;
    let label = "";
    let tradeLog = null;

    const nowTime = moment();
    const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');

    //取引所の稼働状況を確認
    let health = await bitflyer.fetch2('getboardstate');
    if (health.state !== 'RUNNING') {
      // 異常ならwhileの先頭に
      console.log('取引所の稼働状況:', health);
      await utils.sleep(tradeInterval * 1000);
      continue;
    }

    //現在価格を取得
    const ticker = await bitflyer.fetchTicker('FX_BTC_JPY');
    const nowPrice = ticker.close;

    //レコードを更新
    algo.records.push(nowPrice);
    algo.records.shift()

    //アルゴリズム用Paramを初期化
    let bbRes = null;
    let totalEva = 0;
    algo.initEva();
    //共通アルゴリズム
    let crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA);
    let psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio)

    //建玉を調べる
    const jsonOpenI = await bitflyer.fetch2('getpositions', 'private', 'GET', {product_code: "FX_BTC_JPY"});
    const openI = utils.chkOpenI(jsonOpenI)

    //共通表示
    console.log('================');
    console.log('time:', strTime);
    console.log('nowPrice: ', nowPrice);


    // 建玉がある場合
    if (openI.side) {
      //建玉の共通表示
      console.log('');
      console.log('建玉内容');
      console.log(openI);

      let diffDays = nowTime.diff(openI.open_date, 'days');
      // swap日数を超えているなら
      if (diffDays >= swapDays) {
        // 建玉を0に戻す
        label = 'swap日数を超えているため建玉をリセット'

        if (openI.side === 'BUY') {
          await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
          flag = 'SELL';

        } else {
          await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
          flag = 'BUY';
        }
        sumProfit += openI.pnl;

      } else {
        // 日数を超えてないなら
        //  利益が出ている場合
        if (openI.pnl > 0) {
          label = '利確'
          bbRes = algo.bollingerAlgo(BBProfit.period, BBProfit.sigma, openI.price);
          totalEva = algo.tradeAlgo(algoWeight)

          //買い建玉で、下降シグナルが出ている
          if (openI.side === 'BUY' && totalEva < -algoThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //売り建玉で、上昇シグナルが出ている
          } else if (openI.side === 'SELL' && totalEva > algoThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';

          }
        } else {
          //  損してる場合
          label = 'ロスカット';

          //日足でアルゴリズム判断
          const dayPeriods = 60 * 60 * 24;
          const lossTimeStamp = nowTime.unix() - dayPeriods * BBLossCut.period;
          let dayRecords = await crypto.getOhlc(dayPeriods, lossTimeStamp);

          crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA, dayRecords);
          psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio, dayRecords);
          bbRes = algo.bollingerAlgo(BBLossCut.period, BBLossCut.sigma, openI.price, dayRecords);
          totalEva = algo.tradeAlgo(algoWeight)

          //損してるのに、買いを持ってて大きなトレンドが下がり兆候
          if (openI.side === 'BUY' && totalEva < -lossCutThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //損してるのに、売りを持ってて大きなトレンドで上がり兆候
          } else if (openI.side === 'SELL' && totalEva > lossCutThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';
          }
        }
      }

      //建玉を精算したなら、
      if (flag) {
        tradeLog = {
          flag: flag,
          label: label,
          sumProfit: sumProfit,
          profit: openI.pnl,
          nowPrice: nowPrice,
          openPrice: openI.price,
          strTime: strTime,
          created_at: nowTime._d,
          openI: openI,
          bollinger: bbRes,
          cross: crossRes,
          psycho: psychoRes,
          totalEva: totalEva,
        };
        mongo.insert(tradeLog);

        console.log('');
        console.log(label);
        console.log(tradeLog);
      }

      // Line通知(閾値を超えたら)
      if (beforeProfit !== null) {
        const profit = openI.pnl;
        const diff = Math.abs(sumProfit + profit - beforeProfit);
        if (diff >= infoThreshold) {
          const message = `\n date: ${strTime}\n sumProfit: ${sumProfit}\n profit: ${profit}\n collateral: ${collateral.collateral}`;
          line.notify(message);
          beforeProfit = sumProfit + profit;
        }
      } else {
        //アラート初期化
        beforeProfit = sumProfit;
      }


    } else {
      //建玉を持ってない場合

      //スワップポイント対応 23:30-0:00 注文しない
      const limitDay = moment().hours(23).minutes(30).seconds(0)
      if (nowTime.isSameOrAfter(limitDay)) {
        console.log(' ');
        console.log('スワップポイント対応中_23:30-0:00');
        //注文を受け付けない while先頭に移動
        await utils.sleep(tradeInterval * 1000);
        continue;
      }

      // 注文する ボリンジャーを使用
      bbRes = algo.bollingerAlgo(BBOrder.period, BBOrder.sigma);
      totalEva = algo.tradeAlgo(algoWeight)

      if (totalEva > algoThreshold) {
        //【買い】で建玉する
        await bitflyer.createMarketBuyOrder('FX_BTC_JPY', orderSize);
        flag = 'BUY';

      } else if (totalEva < -algoThreshold) {
        //【売り】で建玉する
        await bitflyer.createMarketSellOrder('FX_BTC_JPY', orderSize);
        flag = 'SELL';
      }

      //建玉を取得したなら、
      if (flag) {
        label = '建玉取得';

        tradeLog = {
          flag: flag,
          label: label,
          sumProfit: sumProfit,
          nowPrice: nowPrice,
          bollinger: bbRes,
          cross: crossRes,
          psycho: psychoRes,
          totalEva: totalEva,
          strTime: strTime,
          created_at: nowTime._d,
        };
        mongo.insert(tradeLog);

        console.log('');
        console.log(label);
        console.log(tradeLog);
      }
    }

    console.log('');
    console.log('★sumProfit: ', sumProfit);
    console.log('');
    await utils.sleep(tradeInterval * 1000);
  }
})();


ハイパーパラメーターの説明

  • tradeInterval: 取引間隔。最短は60秒。
  • orderSize: 注文数
  • swapDays: 建玉の保持したい日数。超過したら手放す。
  • infoThreshold: Line告知用の金額幅。設定額を超えた損得をするとLine告知する。
  • psychoParam: サイコロジカルラインのアルゴリズムに使用するパラメーター。
    • 期間
    • 比率
  • crossParam: ゴールデンクロス・デッドクロスのアルゴリズムに使用するパラメーター。
    • 短期移動平均線の期間
    • 長期移動平均線の期間
  • BBOrder / BBProfit / BBLossCut: ボリンジャーバンドのアルゴリズムに使用するパラメーター。 建玉取得 / 利益確定 / 損切りごとに判断材料が異なるため分けています。
    • 判断期間
    • 標準偏差
  • algoWeight: 各アルゴリズムの重み(重要度)を設定。 重要度はただの比率なので合計1になるような調整をおすすめ。
  • algoThreshold: 取引判断の閾値。 アルゴリズムの複合判断された値がこの値以上(以下)であれば取引。
  • lossCutThreshold: ロスカット判断の閾値。 複合判断された値がこの値以上(以下)であればロスカット。

分岐、流れの紹介説明

大まかな処理の流れです。

  • 売買スタート
    判断材料とするため、cryptowatchからコード実行前の取引内容を取得する。
    Lineで「自動売買スタート」したことを通知

  • 取引ループを開始
    設定した取引間隔でループを廻す。

  • bitflyerの取引所の稼働状況を確認
    異常であれば、ループの先頭に移動

  • 現在のbitcoinの価格を取得

  • 共通利用アルゴリズム
    クロス関数、サイコロジカル関数の評価をする

  • 保持している建玉(ポジション)の内容を取得する。

  • 建玉を保持している場合、建玉の保持日数を調べる
    保持日数が、指定日数より長ければ、建玉を手放す
    (swap金と、塩漬けされるのを回避するため。)

  • 保持日数が短い場合

  • 利益が出ていれば、ポジションと、アルゴリズム判断により売買

  • 損が出ていれば、日足材料でのアルゴリズム判断によりロスカット
    ロスカットが日足利用なのは、分足だと指標が流動的過ぎ、大きなトレンドで判断が必要と考慮したため。
    実際、昔は分足を使っていたのですが、かなり細かいブレに振り回され利得チャンスを失った上、小さな損を積み上げやすかったです。
    分足、時間足に変更は可能なので調整してみるのもいいかもしれません。

  • 一定額の損得が発生したら、Lineで通知。

  • 建玉を持っていない場合、

  • 日付変更直前の30分なら、取引せずにループ。
    建玉取得して早々にswap金が発生するのを避けるため。

  • 日付直前じゃなければ、アルゴリズム判断により建玉を取得する。

アルゴリズム:algo.js

売買アルゴリズムをまとめています。

const gauss = require('gauss');

module.exports = class Algo {

  constructor(records) {
    this.records = records;

    // 各アルゴリズムの評価ポイント
    //上昇シグナル:+  下降シグナル:-
    this.eva = {
      'psychoAlgo': 0,
      'crossAlgo': 0,
      'bollingerAlgo': 0
    };
  }

  psychoAlgo(range, ratio, list = this.records) {
    //  陽線の割合で売買を判断する

    let countHigh = 0
    //  任意期間の陽線回数をカウント
    for (let i = range; i > 0; i--) {
      const before = list[list.length - i - 1];
      const after = list[list.length - i];

      if (before <= after) {
        countHigh += 1;
      }
    }

    let psychoRatio = 0;
    psychoRatio = countHigh / range;
    if (psychoRatio >= ratio) {
      this.eva['psychoAlgo'] = 1;
    } else if (psychoRatio <= 1 - ratio) {
      this.eva['psychoAlgo'] = -1;
    }

    return psychoRatio;
  }


  crossAlgo(shortMA, longMA, list = this.records) {
    //ゴールデン・デッドクロスで売買を判断する

    //移動平均作成
    const prices = new gauss.Vector(list);
    const shortValue = prices.ema(shortMA).pop();
    const longValue = prices.ema(longMA).pop();

    if (shortValue >= longValue) {
      this.eva['crossAlgo'] = 1;
    } else if (shortValue < longValue) {
      this.eva['crossAlgo'] = -1;
    }

    return {'shortValue': shortValue, 'longValue': longValue};
  }


  bollingerAlgo(period, sigma, price = this.records.slice(-1)[0], list = this.records) {
    //  ボリンジャーバンド

    const prices = new gauss.Vector(list.slice(-period));
    //SMA使用
    const sma = prices.sma(period).pop();
    const stdev = prices.stdev()

    const upper = Math.round(sma + stdev * sigma);
    const lower = Math.round(sma - stdev * sigma);

    if (price <= lower) {
      this.eva['bollingerAlgo'] = 1;
    } else if (price >= upper) {
      this.eva['bollingerAlgo'] = -1;
    }

    return {'upper': upper, 'lower': lower}
  }


  tradeAlgo(weight) {
    //  重み付けして総合的な取引判断

    let totalEva = 0
    //評価ポイントにそれぞれの重みを掛けて足し合わせる
    for (const [key, value] of Object.entries(this.eva)) {
      totalEva += value * weight[key];
    }

    totalEva = Math.round(totalEva * 100) / 100

    return totalEva
  }


  initEva() {
    //全評価ポイントを初期化
    Object.keys(this.eva).forEach(key => {
      this.eva[key] = 0;
    });
  }

}

複合評価

tradeAlgo()

取引判断は複数のアルゴリズムによる複合判断です。
アルゴリズムごとにそれぞれ評価ポイントを保持。
各アルゴリズムは材料データと設定パラメータにより、-1か、+1どちらかの評価をつける。
正数(+1)は上昇トレンド
負数(-1)は下降トレンド
各アルゴリズムごとに評価ポイントとその重みで掛け算し、最後に全て足し合わせて総合評価ポイントを算出します。

app.js内で閾値より、総合評価ポイントの絶対値が上回っていれば取引を実行。
買/売どちらのポジションで取引するかは、状況に応じてapp.jsで判断。

アルゴリズムの追加について

今後、新たなアルゴリズムを追加したい場合は以下の手順を参考。

  • Algoクラス

    • this.eva(メソッド名と同じ評価ポイントの追加)
    • methodとしてアルゴリズムの追加
  • app.js

    • 重み付けの追加
    • 評価させたい箇所でメソッドを追加
      (恐らく共通アルゴリズムとしてcrossAlgo()などと同じ箇所が多いと思います。)

ボリンジャーバンド

bollingerAlgo()

ボリンジャーバンドは、移動平均線と標準偏差を使った判断アルゴリズム。
ざっくり、標準偏差の絶対値が大きければ大きいほど平均に回帰する力が強くなるっていう考え方。
細かく触れないですが、こちらの説明が分かりやすいです。
マネックス証券解説

4つの変数を使用。

  • 判断期間
  • 閾値にする標準偏差
  • 判断したい値段
  • 値動きリスト

値動きリストから判断したい期間を取り出す。
次に、取り出したリストをもとに指定した標準偏差のupper値とlower値を算出する。
最後に、算出した上下の標準偏差帯より、価格がはみ出していれば評価ポイントをつける。

lower値より価格が低い場合
その価格はトレンドより低めに付けられているので、上昇に転じやすい。
上昇トレンドとして、+1をつける

upper値より価格が高い場合
その価格はトレンドより高めに付けられているので、下降に転じやすい。
下降トレンドとして、 -1をつける

サイコロジカルライン

psychoAlgo()

投資家心理を利用したアルゴリズム判断。
売り買いのどちらかが連続して偏った場合、更にその傾向は続く、またはそろそろ逆が出ると判断して多くの売買が行われることで価格変動を予想するアルゴリズム。
このページが分かりやすいです。
マネックス証券解説

3つの変数を使用。

  • 判断範囲
  • 判断する比率
  • 値段リスト

設定期間の値段リストに絞り込み、
一つ前の値段より上昇している値段の数と全体の値段の割合を調べる。
割合値と判断する比率を利用比較して、上昇トレンド、下降トレンドと評価ポイントを付ける。

ゴールデンクロス、デッドクロス

crossAlgo()

長期移動平均線が、短期移動平均線を下から上に突き抜けることをゴールデンクロス。その逆がデッドクロス。
ゴールデンは上昇トレンドになるサインで、デッドクロスは下降トレンドのサイン。

3つの変数を使用。

  • 短期期間
  • 長期間
  • 値段リスト

上記説明の通り、短期移動平均が長期移動平均より上なら上昇トレンドとして評価ポイント+1をつける。
その逆ならデッドクロスとして評価ポイントに-1をつける。
なお、よりトレンドの勢いを加味したかったので、直近値動きをより重視する指数平滑移動平均線(Exponential Moving Average)を使用しました。

OHLCの取得:crypto.js

プログラム実行直後のOHLC取得にcryptowatchAPIを使用。
OHLCはopen/high/low/closeとローソク足データのことです。

Line通知:line.js

取引開始と、一定額以上の損得が発生する毎にLineから通知をします。

Line Nnotifyに関してはこちらのページが分かりやすいです。
LINE notify を使ってみる

取引内容の保存:mongo.js

MongoDBで、売買取引を記録。
取引内容はbitflyerAPIからのjson取得で定形データでは無いので、NoSQLのMongoDBを選択しました。

Dockerコンテナによる稼働ですが、volumesでデータを永続化しています。
最初のDockerコンテナ立ち上げで、volumeディレクトリ:container_dataが作成されます。

その他:utils.js

その他のユーティリティ関数をまとめています。

Apple Home連動

iphone,AppleWatchから1タップで、AWS上のプログラムのON/OFFできるようにしました。

  1. AWS EC2インスタンスにDockerを展開
  2. 自宅のRaspberry Piにhomebridge展開し、実行のshellファイルを紐付ける

詳しくは以下を参考ください。
公式homebridge

Philips_HueをAPI連動!〜ラズパイをHomeKit化する

IntelliJで取引DBを見る

MongoDBに保存した取引内容ですが、閲覧はIDEの利用をおすすめします。
直接MongoDBからの閲覧は、json形式のためかなり辛いです。
IntelliJなどのIDEでしたら、いい感じに見やすくしてくれます。
スクリーンショット 2020-09-20 10.18.45.png
スクリーンショット 2020-09-20 10.11.57.png
IntelliJの設定方法は、過去記事を参照ください。
IntelliJからAWSを操作する設定方法まとめ

注意点

最初のDocker立ち上げ

MongoDBが書き込み可能になるのに時間かかります。
volumeディレクトリのcontainer_dataを作成するためです。
余裕持って初回起動して、しばらくしても書き込みされなければ、再度Dockerを立ち上げ直してください。
私の場合ですが、2回目以降は問題なく稼働しています。

Docker Volumeのcontainer_dataはroot権限

作成されるvolumeディレクトリのcontainer_dataはroot権限で作られます。削除したい際はsudoを付けてください。
私のうっかり経験ですが、Dockerを再ビルドする際に、このディレクトリ権限に気付かずエラーになって少しハマりました。

終わりに

楽して不労所得の夢は実現しません:skull::skull:
しつこいですが、必ず儲かるわけではないですし、自己責任でお願いします。:point_up_tone3:

ひょっとしたら、
私のアルゴリズムや、パラメーターがイマイチナだけで、誰かが追加したアルゴリズムにより儲かりだすかもしれません。。。

そんな時は、こっそりと教えて下さいね:musical_note:
それでは、よろしければホビーとしてほどほどに楽しんでみてください。

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

理想を追い求めたCI/CDパイプラインをTerraformで実装するためのポイント(production環境編)

はじめに

前回の記事の続編。
前回は、クロスアカウントで、ビルド環境でビルドしたコンテナイメージをstage環境にコピーする部分だったが、今回の範囲は、それをproduction環境にコピーする部分だ。

こうだったのが……
crossaccountpipeline.png

こうじゃ!
crossaccountpipeline2.png

前提知識としては、前回のパイプラインを理解できていること。
新しい概念は出てこないので、サクサク書いていく。

プロバイダの設定は前回と同じと考えてほしい。
本来だったらプロバイダを、dev, stage, production とするところを、簡略化するために今回も build_account, service_account としている。

また、ECRのコピー元を一意に特定するためにCommitIDを使っている。
このため、マージコミットはFast Forwardを使うようにする。Non Fast Forwardの場合は別の方法でCommitIDを連携する必要があるため注意が必要だ。

変更点

クロスアカウント設定等は前回と同様に行えばよい。
今回は、差分としてEventBridgeの設定部分を書いておく。

EventBridgeの設定(build_account側)

################################################################################
# EventBridge(Build Account)                                                   #
################################################################################
resource "aws_cloudwatch_event_rule" "codecommit_merge_build_account" {
  provider = aws.build_account

  name          = local.event_rule_name
  description   = "CodeCommit Merge Event Rule"
  event_pattern = data.template_file.event_pattern_build_account.rendered
}

resource "aws_cloudwatch_event_target" "event_bus_build_account" {
  provider = aws.build_account

  rule      = aws_cloudwatch_event_rule.codecommit_merge_build_account.name
  target_id = local.event_target_id
  arn       = "arn:aws:events:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:event-bus/default"
  role_arn  = aws_iam_role.eventbridge_build_account.arn
}

data "template_file" "event_pattern_build_account" {
  template = file("${path.module}/event_pattern_build_account.json")

  vars = {
    build_account_id          = data.aws_caller_identity.build_account.account_id
    codecommit_repository_arn = data.aws_codecommit_repository.application.arn
  }
}

################################################################################
# IAM Role for EventBridge                                                     #
################################################################################
resource "aws_iam_role" "eventbridge_build_account" {
  provider = aws.build_account

  name               = local.eventbridge_iam_role_name
  assume_role_policy = data.aws_iam_policy_document.eventbridge_build_account_assume.json
}

data "aws_iam_policy_document" "eventbridge_build_account_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "events.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "eventbridge_build_account" {
  provider = aws.build_account

  role       = aws_iam_role.eventbridge_build_account.name
  policy_arn = aws_iam_policy.eventbridge_build_account_custom.arn
}

resource "aws_iam_policy" "eventbridge_build_account_custom" {
  provider = aws.build_account

  name        = "SendEventToServiceAccountPolicy"
  description = "Send Event to Service-Account Policy"
  policy      = data.aws_iam_policy_document.eventbridge_build_account_custom.json
}

data "aws_iam_policy_document" "eventbridge_build_account_custom" {
  version = "2012-10-17"

  statement {
    effect = "Allow"

    actions = [
      "events:PutEvents",
    ]

    resources = [
      "arn:aws:events:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:event-bus/default",
    ]
  }
}

event_pattern_build_account.json
{
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "resources": [
    "${codecommit_repository_arn}"
  ],
  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },
  "source": [
    "aws.codecommit"
  ]
}

EventBridgeの設定(service_account側)

################################################################################
# EventBridge(Service Account)                                                 #
################################################################################
resource "aws_cloudwatch_event_rule" "codecommit_merge_service_account" {
  provider = aws.service_account

  name          = local.event_rule_name
  description   = "CodeCommit Merge Event Rule"
  event_pattern = data.template_file.event_pattern_service_account.rendered
}

resource "aws_cloudwatch_event_target" "event_bus_service_account" {
  provider = aws.service_account

  rule      = aws_cloudwatch_event_rule.codecommit_merge_service_account.name
  target_id = local.event_target_id
  arn       = aws_codepipeline.service_account.arn
  role_arn  = aws_iam_role.eventbridge_service_account.arn
}

data "template_file" "event_pattern_service_account" {
  template = file("${path.module}/event_pattern_service_account.json")

  vars = {
    build_account_id          = data.aws_caller_identity.build_account.account_id
    codecommit_repository_arn = data.aws_codecommit_repository.application.arn
  }
}

################################################################################
# IAM Role for EventBridge                                                     #
################################################################################
resource "aws_iam_role" "eventbridge_service_account" {
  provider = aws.service_account

  name               = local.eventbridge_iam_role_name
  assume_role_policy = data.aws_iam_policy_document.eventbridge_service_account_assume.json
}

data "aws_iam_policy_document" "eventbridge_service_account_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "events.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "eventbridge_service_account" {
  provider = aws.service_account

  role       = aws_iam_role.eventbridge_service_account.name
  policy_arn = aws_iam_policy.eventbridge_service_account_custom.arn
}

resource "aws_iam_policy" "eventbridge_service_account_custom" {
  provider = aws.service_account

  name        = "StartPipelinePolicy"
  description = "Start CodePipeline Policy"
  policy      = data.aws_iam_policy_document.eventbridge_service_account_custom.json
}

data "aws_iam_policy_document" "eventbridge_service_account_custom" {
  version = "2012-10-17"

  statement {
    effect = "Allow"

    actions = [
      "codepipeline:StartPipelineExecution",
    ]

    resources = [
      "arn:aws:codepipeline:ap-northeast-1:${data.aws_caller_identity.service_account.account_id}:*",
    ]
  }
}

event_pattern_service_account.json
{
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "account": [
    "${build_account_id}"
  ],
  "resources": [
    "${codecommit_repository_arn}"
  ],
  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },
  "source": [
    "aws.codecommit"
  ]
}

ポイント

ポイントになるのはイベントパターンの

  "detail": {
    "referenceType": [
      "branch"
    ],
    "event": [
      "referenceUpdated"
    ],
    "referenceName": [
      "production"
    ]
  },

の部分で、これによって、productionブランチの更新を拾える。
つまり、productionブランチに対するマージコミットのタイミングでイベント連携され、パイプラインを起動できるということだ。

CodeCommitのイベント連携であるため、CommitIDもちゃんとservice_account側に通知されるので、あとはbuildspec側で

phases:
  pre_build:
    commands:
      (中略)
      - IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)

CODEBUILD_RESOLVED_SOURCE_VERSION の環境変数も参照可能になる。

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

C#のLambdaプロジェクトを作成してVisual Studio for Macでデバッグ実行する

はじめに

MacでC#のLambdaプロジェクトを作成し、Visual Studio for Macでテストプロジェクトをデバッグ実行した記録です。

できたこと

  • C#のLambdaプロジェクトを作成
  • AWS Lambdaへのデプロイ
  • AWS Lambdaでのテスト
  • Visual Studio for Macでソリューション化
  • テストプロジェクトのデバッグ実行

AWS Lambda上で動くプログラムをアタッチしてデバッグしたわけではありません。
テストプロジェクトをローカルPC上でデバッグ実行しました。

準備

アクセスキー ID とシークレットアクセスキーを設定

PCからAWSにアクセスできるようにアクセスキー ID とシークレットアクセスキーを設定します。

.NET Core CLI

  • プロジェクトテンプレートの追加
    dotnet new -i Amazon.Lambda.Templatesコマンドを実行して、.NET Core CLIにAWS Lambdaのテンプレートを追加します。

  • Amazon.Lambda.Tools .NET Core Global Tool のインストール
    dotnet tool install -g Amazon.Lambda.Toolsコマンドを実行します。

参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/csharp-package-cli.html

プロジェクトの作成

空のLambdaプロジェクト作成するコマンドを実行します。
dotnet new lambda.EmptyFunction --name {メソッド名}
(以下、{メソッド名}に「SelectFunction」と指定した想定で説明します。)

色々とファイルが生成されますが、Lambda関数を実行するとFunction.csのFunction.FunctionHandler()メソッドが呼び出されることになります。
デフォルトでは、引数inputで受け取った文字列を大文字に変換して戻り値に返す処理となっています。

Function.cs
using Amazon.Lambda.Core;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace SelectFunction
{
    public class Function
    {
        /// <summary>
        /// A simple function that takes a string and does a ToUpper
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public string FunctionHandler(string input, ILambdaContext context)
        {
            return input?.ToUpper();
        }
    }
}

参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/csharp-package-cli.html

デプロイ

デプロイしてみます。

  • .csprojファイルがあるディレクトリに移動します。
  • dotnet restoreコマンドを実行します。
  • dotnet lambda deploy-functionを実行します。

    • Enter AWS Region:と表示されるので、デプロイ先のリージョンを入力。
    • Enter Function Name:と表示されるので、作成したいLambda関数名を入力。
    • Select IAM Role that to provide AWS credentials to your code:とIAMロールの一覧が表示されるので、使用するIAMロールを選択。

デプロイに成功すると、AWS Lambdaのコンソールに作成したLambda関数が表示されます。
スクリーンショット 2020-09-21 8.49.12.png

AWS Lambda上でテスト

AWS LambdaコンソールでデプロイしたLambda関数を選択し、「テストイベントの設定」をクリックします。
スクリーンショット 2020-09-21 8.49.59.png

「イベント名」欄に任意の名称を入力し、その下の編集エリアに引数に渡す情報をJsonで入力して保存ボタンをクリックします。
スクリーンショット 2020-09-21 8.50.49.png

先ほど「イベント名」欄に入力した名前を選択してテストボタンをクリックすると、実行結果欄にFunction.csのFunction.FunctionHandler()メソッドの戻り値が表示されます。
スクリーンショット 2020-09-21 8.51.39.png

Visual Studio for Macでソリューション化

Visual Studio for Macでコーディングやデバッグ実行をしたいのでソリューションを作成します。
まずは、空のソリューションを作成します。

スクリーンショット 2020-09-21 8.17.17.png

プロジェクトの作成で作成されたディレクトリ構成に合わせてソリューションの場所を指定します。ディレクトリ構成はお好きにどうぞ。
スクリーンショット 2020-09-21 8.23.37.png

ソリューションウィンドウでソリューション名を右クリック→[追加]→[既存のプロジェクト]を選択し、Lambdaプロジェクトとそのテストプロジェクトを追加します。
スクリーンショット 2020-09-21 9.03.10.png

テストの実行

テストプロジェクトをスタートアッププロジェクトに設定します。

単体テストウィンドウを表示し、テストメソッドを右クリック→[テストのデバッグ]をクリックすると、テストメソッドをデバッグ実行できます。

スクリーンショット 2020-09-21 9.07.48.png

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

意外と簡単!EC2上のWebアプリを独自ドメイン化&SSL化

さようならFirefox Send。あなたは私のT4gインスタンスの中で永遠に生き続けます!!
でEC2上にWebアプリケーションを構築しました。
欲が出て独自ドメイン化&SSL化をしてみました。

ドメインの取得

freenomで.tk.ml.ga.cf.gqドメインが無料で取得できます。
欲しい物を探してドメインを取得します。
image.png

Route53の設定(ホストゾーンの設定)

ホストゾーンを追加します。取得したドメイン名をパブリックホストゾーンとして登録します。
image.png

登録すると、NSレコードが作成されます。ここで4つのDNSサーバーのドメインが自動で指定されます。
image.png

ここで指定されたドメインをfreenomに設定します。

Services -> My Domains
Manage Domains
Management Tools -> Nameservers
image.png

これでRoute53でドメインが管理されます。反映までに結構時間がかかります。

ACM(AWS Certificate Manager)の設定

SSL証明書を発行します。
バージニア北部(us-east-1)リージョンを選択します。

証明書のリクエスト
image.png

ドメインは*.を先頭に付けてワイルドカード証明書としましょう
image.png

DNSの検証を選択します。
image.png

次々進み、検証のところで、Route 53でのレコードを作成ボタンを押します。
image.png

しばらくすると証明書が発行済となります。これもちょっと時間がかかります。
image.png

CloudFrontの設定

Create Distributionから作成を進めます。
WebのところのGet Startedを押します。

Origin Domain NameにEC2のパブリック IPv4 DNSを入力します。
Origin IDは自動で入力されますが、うまく入力されるときとされない時があります。。何度もOrigin Domain Nameを入れたり消したりしてるとそのうちうまっくいくと思います。
image.png

HTTP PortHTTPS PortをEC2で起動しているWebアプリに合わせます。(今回はHTTPが1443ポート)
Viewer Protocol PolicyRedirect HTTP to HTTPSに、Allowed HTTP MethodsGET, HEAD, OPTIONS, PUT, POST, PATCH, DELETEに変更します。
image.png

Origin Request PolicyManaged-AllViewerにします。
image.png

Alternate Domain Names(CNAMEs)に使用するドメイン名(moritalous.ga)を入力し、SSL CertificateCustom SSL CertificateにしてACMで作成した証明書を選択します。
image.png

Route53の設定(サブドメインの設定)

サブドメインの設定を行います。

ホストゾーンの選択 -> レコードを作成
シンプルルーティングを選択します。
image.png

シンプルなレコードを定義を押します。
image.png

レコード名を入力し、値/トラフィックのルーティング先CloudFrontディストリビューションへのエイリアスを選択し、作成済みのディストリビューションを選択します。
image.png

レコードを作成ボタンを押します。
image.png

設定は以上です。

動作確認

CloudFront発行のURL(HTTPS)

OK
CloudFront発行の証明書が使用されています。
image.png

CloudFront発行のURL(HTTP)

HTTPSにリダイレクトされ表示。OK

Route53発行のURL(HTTPS)

OK
こちらはACMで発行した証明書が使用されています。
image.png

Route53発行のURL(HTTP)

HTTPSにリダイレクトされ表示。OK

うまくいきました。

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

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

▶この記事の対象者◀

この記事は凡人向けです!

筆者のスペック

▶業界歴

   2年目

▶資格

   なし

▶経験のあるサービス

   RDSAurora DynamoDB Redshift Lambda Glue CloudWatch

▶学習期間

   100時間程度(週5時間・半年ぐらい)

受験のきっかけ

業務の中でAWSを利用していましたが、AWSがまったくわからなかったので学習を始めました。
学習するならついでに資格取得も挑戦してみようと思って受験しました。

結果

1回目は不合格になりました。。。
image.png

2週間後にリベンジして合格できました!!
image.png
image.png

学習方法

▶qiitaでAWSSAAに関する記事を読みまくる

どんな学習方法があるか調査しました。
1ヵ月で取得した方や1週間で取得する方も居ましたが、
私と同じ様な凡人は3ヵ月~半年かけて勉強していきましょう。

▶王道の学習サービスをフル活用

徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書 徹底攻略シリーズ
¥2,200~2,500
平日の通勤時間、昼休憩時間に2週ぐらいしました!
AWSのサービスに関する基礎知識からベストプラクティスまで網羅しています!
模擬試験も付いているのでこれだけで合格できる方も居るかもしれません。
私はAWSの知識が無かったので、この本で基礎部分を学ぶことが出来ました!
例:リージョン、AZ、VPC、ELBなど

この本で学習する注意点としては最新のAWSバージョンに対応できない部分です。
AWSは頻繁に仕様が変わるため、
「この本ではできないと記載があったのに今はできるよ!」とかあります。
例:Lambda→RDSは諸事情によりアンチパターンだったが、RDSProxyの登場で改善された。

これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(SAA-C02試験対応版)
¥12,000 ←頻繁にセール価格になり、80~90% OFFで購入できます!
AWSSAA対策で1番人気の教材だと思います!
ハンズオン形式でAWSサービスの利用方法を学べるため、実務経験がない方にもオススメです!
こちらも模擬試験が3回分付属している為、とても優良です。

私は60%ぐらいまで行いました。
AWSの触り方を手取り足取り教えてくれるので、初学者でも安心です!
AWSは有料サービスですが、講師の方が課金が発生しそうな部分は注意してくれるので、
知らないうちに沢山の請求が来ちゃった!!ってことにはならないと思います。

【SAA-C02版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)
¥3,600
模擬試験が6回分セットの教材です!
高難易度と書いてありますが、本試験ではこちらの教材と同レベルの問題がバンバン出ます!

私はこちらをメインで行っていました。
1回目はめちゃくちゃ難しいので合格ライン突破するのは難しいと思います。
※逆に初見で合格ライン突破できる方は本試験に挑戦してもいいかもしれません。。。

オススメの学習方法
・1週目
試験 → すべて見直し(正解も自分の考え方があっていたか確認) → 次の試験
・2週目、3週目
試験 → 不正解だけ見直し → 次の試験

ベストは2週目で合格ラインを超え、3週目で80~90%であれば本試験合格の確率は高いです。

AWS WEB問題集で学習しよう
¥4,280(税抜き) ←プラチナプラン
こちらも問題集となります!
SAAだけでなくSAP、DVPなどの認定試験の問題もあるのでとても優良です。
プランはいろいろあるので用途に合ったものを選びましょう。

問題は随時追加されているので、後半の問題は必ず解いておきましょう!

こちらの問題集は難易度も高いですが、なにより問題文が本試験に近いです!
Udemyの問題文は本試験と違うので、こちらで本試験に近い問題文で対策することをオススメします!

また、問題は一問一答形式ですぐに見直しが出来るところもいいですね。
空いた時間にできるのでとても便利です!

▶BlackBeltで知識を深める

AWS サービス別資料
無料
BlackBeltと呼ばれているAWS公式の資料群です。
こちらはSAA対策だけでなく、AWSを利用する上でいつでも活用できるとても優良の資料たちです。

サービス別に資料がまとめられていて、サービスの概要からベストプラクティスまで網羅されています!

私は通勤時間などの隙間時間に目を通していました。
資料もとても見やすく、面白いと感じれる資料でした!

最後に

本試験はとても難しかったです。
130分の試験時間があるので、60分ぐらいで一回解き終わると思いますが、
すべて見直しすることをオススメします!
見直しの中で「これじゃなくてこれじゃね?」って解答が複数ありました。
見直しをしていなければ不合格になっていたかもしれません。。。
なんとか合格できたので一安心です(笑)

合格したことでAWSに関する知識に自信もつきましたし、
業務の中でも勉強したことはとても活かされていると実感しています!

現在はアソシエイト3冠を目標にSOAを勉強中です!

みなさんもAWSを勉強して素敵なAWSライフを送りましょう!

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

AWS Step Functionsで5分後にDynamoDBのデータを削除する

はじめに

API Gateway + Lambda + DynamoDBでwebAPIを開発しています。
Lambda関数でDynamoDBへデータを書き込み、そのデータを5分後に削除する必要がありました。
そこで、以下の方法を考えました。
・DyanamoDBのTTL機能を使って5分後にデータを削除する
・データを書き込むLambdaから5分後にデータを削除するLambdaを呼び出す

DynamoDBのTTL機能

まずDynamoDBのTTL機能を調べたのですが、TTLでは有効期限から48時間以内に期限切れのアイテムを削除しますとの記載があり、5分後に必ず削除する設定は不可能でした。
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html

AWS Step Functionsを使う

次に、データを書き込むLambdaからデータを削除するLambdaを呼び出すを検討します。
調べた結果、AWS Step Functionsを使うことで実現できそう。実装してみます。

データを書き込むLambda関数の作成

ランタイムはPython3.8、DynamoDBとStepFunctionsへのアクセス権を付与します。
以下のLambda関数でDynamoDBへデータを書き込み、StateMachineを呼び出します。
userIdをパラメータとしてStateMachineに渡します。

lambda_function.py
import  boto3

client = boto3.client('stepfunctions')
dynamoDB = boto3.resource('dynamodb')

def lambda_handler(event, context):
    # データを書き込むテーブル
    table = dynamoDB.Table('test')
    # 呼び出すStateMachine
    stateMachineArn = 'StateMachineのARN'
    # DynamoDBにデータを書き込む
    table.put_item(Item={'data': data})
    # StateMachineを呼び出す
    input = {"parameters":{"userId":userId}}
    client.start_execution(
        stateMachineArn = stateMachineArn,
        input = json.dumps(input)
        )
    return {'statusCode': 200, 'body': "writeData"}

StateMachineの作成

以下の定義でStateMachineを作成します。
StateMachineにLambdaへのアクセス権を付与してください。

{
  "StartAt": "wait_5_miutes",
  "States": {
    "wait_5_miutes": {
      "Type": "Wait",
      "Seconds": 300,
      "Next": "deleteData"
    },
    "deleteData": {
      "Type": "Task",
      "Resource": "データを削除するLambda関数のARN",
      "Parameters": {
          "userId.$": "$.parameters.userId"
      },
      "End": true
    }
  }
}

スクリーンショット 2020-09-20 16.04.15.png

データを削除するLambda関数の作成

StateMachineから、以下のLambda関数を実行することでデータを削除します。

lambda_function.py
import boto3

dynamoDB = boto3.resource('dynamodb')
table = dynamoDB.Table('test')

# StateMachineで呼び出される
def lambda_handler(event, context):
    # StateMachineからパラメータを受け取る
    userId = event['userId']
    # 削除対象のデータを探す
    searchResult = table.get_item(Key={'userId': userId})
    if "Item" in searchResult:
        table.delete_item(Key={'userId': userId})
    return {'statusCode': 200, 'body': "deleteData"}

おわりに

AWS Step Functionsを使うことで、DynamoDBへデータを書き込み、そのデータを5分後に削除するが実現できました。
もっと複雑な処理もできそうなので試してみたいです。

参考

https://qiita.com/ketancho/items/147a141c9f8a6de86c97
https://aws.amazon.com/jp/getting-started/hands-on/create-a-serverless-workflow-step-functions-lambda/

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

Access denied for user 'root'@'localhost' (using password: YES)のエラー

状況

Ruby on Rails 6.0.3.2
EC2へのデプロイ作業中のエラー
MySQLへのログインはできるので正しいパスワードは把握できている。

DBの作成時にエラーが出る

本番環境で以下のコマンドでDB作成を試みる。

rails db:create RAILS_ENV=production

しかし以下のようなエラーが発生。

rails aborted!
Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: YES)
/var/www/<アプリ名>/bin/rails:9:in `<top (required)>'
/var/www/<アプリ名>/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => db:create
(See full trace by running task with --trace)

何が問題なのか

MySQLへ接続時のパスワードが間違っている。

Access denied for user 'root'@'localhost' (using password: YES)

上記の文の意味はパスワードは入力されているがパスワードが間違っているという意味である。

解決方法

環境変数の設定が間違っている可能性がある。
特にenvironmentファイルに記入した環境変数とパスワードを囲むシングルクォートが全角だとエラーになる。(シングルクォートを含んだパスワードと見なされるため)
環境変数とパスワードを表示するコマンドを入力した時、パスワードと環境変数がシングルクォートで囲まれているかどうかで確認できる(半角だとシングルクォートが出ない)。

まとめ

タイトルのようなエラー文が表示された時はパスワードが間違っているので要確認。
 

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

AWSのrootユーザー権限が必要となるタスク(Tasks that require root user credentials)

本記事概要

AWSアカウントについて、公式が
・極力rootユーザーを使うのではなく、IAMで作ったユーザーを使いなさい
・しかしrootユーザー権限でしかできないタスクもある
と言っているので「rootユーザー権限でしかできないタスク」を日本語訳して残しておく。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_root-user.html

rootユーザー権限でしかできないタスク

以下URLの「Tasks that require root user credentials」部分
https://docs.aws.amazon.com/general/latest/gr/root-vs-iam.html#aws_tasks-that-require-root
※特に英語力はないので自身に理解できる程度の意訳の可能性あり。
※URL先の内容は変更されている可能性あり。あくまで記事投稿時点のもの(2020/09/20)
※自身用に多少の注釈も追記している。

  • アカウント設定
    アカウント名、rootユーザーパスワード、メールアドレス。
    連絡先情報、お支払い通貨の設定、リージョンといった他の情報は含まない。

  • 特定の請求書表示
    アクセス許可名「aws-portal:ViewBilling」を持つIAMユーザーは
    AWSヨーロッパからはVAT請求書を表示およびダウンロードできるが、
    AWS IncまたはAmazon Internet Services Pvt Ltd(AISPL)からはできない。
    ※以下用語
    「aws-portal:ViewBilling」:Billing and Cost Management コンソールのページを表示するアクセス権限
    VAT:付加価値税。日本の消費税のようなもので、EUやアジアなどの国で、物やサービスの購買時に課せられる間接税のこと。
    AISPL:インドで AWS サービスのリセラーとして機能する現地法人。

  • アカウントの閉鎖
    アカウントの閉鎖はAWSに連絡して代行してもらうということは不可。

  • IAMユーザー権限の復元
    唯一のadministrator権限のIAMで誤って自身の権限を取り消してしまった場合は、
    rootユーザーとしてサインインして、ポリシーを編集し、権限を復元できる。

  • AWSサポートプランの変更、キャンセル
    より詳しい情報を知りたいなら「IAM for AWS Support」を見るべし。
    個人で触ることはなさそう。

  • リザーブドインスタンスマーケットプレイスに出品者として登録すること
    リザーブドインスタンスマーケットプレイス:さまざまな期間と料金オプションの未使用スタンダード リザーブドインスタンス の販売をサポートするプラットフォーム
    個人で触ることはなさそう。

  • CloudFrontのキーペア作成
    CloudFront:AWSが提供する高速コンテンツ配信ネットワーク (CDN) サービス。
    そのサービス作成時にプライベートキーの取得が必要になる。

  • MFA Deleteを有効にするようにAmazon S3バケットを構成すること
    S3 MFA Delete:MFA(多要素認証)削除

  • 無効なVPC IDまたはVPCエンドポイントIDを含むAmazon S3バケットポリシーの編集、削除
    バケットポリシー:S3のバケットおよびバケット内のオブジェクトにアクセス許可を付与できる。
    ※ココはどういうことなのか詳細には理解しきれていない。

  • GovCloudへのサインアップ
    アメリカ人かつ政府関係者向けのサービスなので個人で触ることはなさそう。

以上。

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

冴えないAWS環境の育てかた α を育ててみた�<準備編>

はじめに

クラスメソッドさんの技術ブログ「Developers.io」にてこの様な記事が投稿されました
https://dev.classmethod.jp/articles/saenai-aws-1/

要約すると
Saenai_001.png

この様な稀によく見るAWS環境を「Well Architected Framework」を意識しつつ、

  • AWSアカウントの育てかた
  • ネットワークの育てかた
  • サーバーの育てかた
  • データストアの育てかた

の4つに分けて育てていくという記事です。

ちなみに最終的にこの様なアーキテクチャに育っていきます
Saenai_104-960x1011.png

今日から約1〜2ヶ月間、力試しという形で自分なりに+αでできることも加えつつ、このアーキテクチャに挑戦してみます!

準備編

今日は準備編ということで、元となるアーキテクチャを設計していこうと思います。

スクリーンショット 2020-09-20 23.09.18.png

デプロイ対象のアプリケーションはフレームワークの練習用に作成した。Laravelの掲示板アプリを使用しました。

ネットワーク作成

まずはCloudfomationを利用して、VPC、パブリックサブネット、プライベートサブネットを作成。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'false'
  eip:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
  subnetPub1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      AvailabilityZone:
        Fn::Select:
        - '0'
        - Fn::GetAZs:
            Ref: AWS::Region
      VpcId: !Ref VPC
  subnetPub2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      AvailabilityZone:
        Fn::Select:
        - '1'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  subnetPrv1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      AvailabilityZone:
        Fn::Select:
        - '0'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  subnetPrv2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      AvailabilityZone:
        Fn::Select:
        - '1'
        - Fn::GetAZs: !Ref AWS::Region
      VpcId: !Ref VPC
  Nat:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
        - eip
        - AllocationId
      SubnetId: !Ref subnetPub1
    DependsOn: eip
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  RouteTablePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  gw:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW
  subnetRoutePub1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref subnetPub1
  subnetRoutePub2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref subnetPub2
  subnetRoutePrv1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref subnetPrv1
  subnetRoutePrv2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref subnetPrv2
  routePublic:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref IGW
    DependsOn: gw
  routePrivate:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate
      NatGatewayId: !Ref Nat
    DependsOn:
    - Nat
    - subnetRoutePrv1
    - subnetRoutePrv2

完全なinfrastructure as codeに憧れを持ってますが、コード管理しちゃうと手作業の差分とかの管理が難しそうで、まだ手をつけられてないです...

サーバー

適当にEC2サーバーを立てちゃいます。
インスタンスタイプはとりあえず無料枠の「t2.micro」

とりあえずSSH接続

#とりあえず更新
$ sudo yum update -y

#Apach,php,Mysqlのインストール
$ sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
$ sudo yum install -y httpd mariadb-server

#Apache ウェブサーバーを起動
$ sudo systemctl start httpd

#システムがブートするたびに Apache ウェブサーバーが起動するように設定
$ sudo systemctl enable httpd

composerのインストール

$ curl -sS https://getcomposer.org/installer | php

# パスを通す
$ sudo mv composer.phar /usr/local/bin/composer

gitからアプリを持ってくる

$ sudo yum install git
$ cd /var/www
$ git clone https://github.com/mkoki0422/Laravel_keiziban.git

Apacheの設定

DocumentRootを変更

$ sudo nano /etc/httpd/conf/httpd.conf

DocumentRoot "/var/www/html"
→
DocumentRoot "/var/www/Laravel-keiziban/public"
に変更

.htaccessの有効化

#http.confファイルに追加
<Directory /var/www/Laravel-keiziban/public>
 AllowOverride All
</Directory>

再起動して、設定を適用

$ sudo systemctl restart httpd

vendorディレクトリの作成

$ composer install

.envファイルの作成

#.envファイルの作成
$ cp .env.example .env

# .envのAPP_KEYの生成
$ php artisan key:generate

RDS

サブネットグループ作成

RDSを構成する前にRDSを配置するサブネットグループを作成しましょう。

マネジメントコンソールの「サブネットグループ」から「DBサブネットグループの作成」に進みましょう。
注意 RDSはMultiAZ構成をしない場合でも、2つ以上のAZを指定しないといけません!

スクリーンショット 2020-09-20 23.29.27.png

データベース用セキュリティグループ

DBサーバーに適用するためのセキュリテイグループも事前に作成しておきます。

インバウンドルールのタイプを「MYSQL/Aurora」、ソースはEC2で適用しているセキュリティグループを選択する方法と、IPアドレスを選択する方法がありますが、今回はセキュリティグループをソースとして指定します。

スクリーンショット 2020-09-20 23.34.58.png

DB構築

実際にDBを構築していきます!

エンジンは「MySQL」
テンプレートは「開発/テスト」もしくは「無料利用枠」のどちらかを選択します。
後々MultiAZ構成にするかもしれませんので、今回は「開発/テスト」を選択しました。

注意なるべくコストを抑えたいけど、「開発/テスト」を選択したい場合はDBインスタンスサイズをバースト可能なクラスである「db.t2.micro」を選択しましょう。
デフォルトだと月額345.86 USDかかってしまいます....

「接続」からVPCの指定
「追加の接続設定」からサブネットグループの指定、セキュリティグループの指定、マスターDBを設置するAZの指定をしましょう。
追加設定から、実際に使用するDB名も指定しておきます。

項目 設定値
エンジン MySQL
テンプレート 開発/テスト OR 無料利用枠
接続 今回作成したVPC
セキュリティグループ 先ほど作成したSG(Defaulは削除して下さい)
サブネットグループ 先ほど作成したサブネットグループ
AZ マスターDBを配置したいAZを指定
DB名 実際に利用するDB名
マスタユーザー マスターユーザー名とパスワード

LaravelのDB接続設定

DBサーバーの構築が完了しましたので、Laravelの設定を変更してRDSに接続していきます。

.envの設定

webサーバーにSSHログイン後、.envファイルを変更していきます。

cd /var/www/Laravel-keiziban
sudo nano .env

.envファイルに四項目を設定して下さい。

項目 設定値
DB_HOST RDSのエンドポイント
DB_DATABASE RDSを構築した際に指定した、DB名
DB_USERNAME RDSを構築した際に指定した、マスターユーザー名
DB_PASSWORD RDSを構築した際に指定した、マスターパスワード

migrateの実行

php artisan migrate

ここでエラーがでなければ接続完了です!

実際にアプリを触ってみて、確認してみましょう。

Route53

一旦ゴールデンイメージ(AMI)を作成しまして、Webサーバーを複製します。

スクリーンショット 2020-09-21 0.38.03.png

使ってなかったドメインがあったので、複数値ルーティングで2つのWEBサーバーをルーティングさせました。

明日の予定

今日は準備編ですので、とりあえずデプロイして、RDSに接続、Route53でルーティングさせるところまでやってみました。
明日は

  • Route53 / ヘルスチェックの有効化
  • CloudFront / ディストリビューションの作成
  • CloudFront / 追加のメトリックを有効化
  • CloudFront / アクセスログの取得
  • CloudFront / 特定地域からのアクセスをブロック
  • CloudFront / カスタムエラーレスポンスとしてSorry Pageを構成
  • CloudFront / 監視
  • ACM / サーバー証明書の作成
  • ACM / CloudFrontへの関連付けと適切なセキュリティポリシーの選択

を進めていこうかと思いますー!

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

Route53でホストゾーンの作成

ドメインの取得

有名なのは、お名前.com
定番の.com.jpもあり、.workだと1円のドメインもあります。
アカウント登録から、ドメイン取得まで10分もかからずに出来ました。

また、無料ドメインだとfreenomがあります。
無料でドメインが取れますが、日本のサイトではないので少し見づらいかも?
こちらも簡単にドメイン取得ができます。

Route53でホストゾーンの作成

  • Route53でホストゾーンの作成

Route53のダッシュボードからホストゾーンの作成に進みます。
Route 53 Console - Google Chrome 2020-09-14 21.14..png

  • ホストゾーンの設定を記載

ドメイン名説明-オプションを記載、
タイプは外部に公開するのでパブリックホストゾーンを選択します。
選択が出来たら、ホストゾーンの作成を押して、完了です。
Route 53 Console Hosted Zones - Google Chrome 2020.png

ホストゾーンの内容設定画面に移行し、ホストゾーンの作成は完了です。

ネームサーバの設定

今回私はお名前.comでドメインを取得したので「ネームサーバの設定をRoute53でしましたよ」とお名前.comで設定しないといけないです。

  • Route53でNSレコードの確認

黒で消してしまっているのですが、赤い枠の中に4つレコードがあります。
そちらをお名前.comで設定します。

無題 - ペイント 2020-09-16 21.01.05.png

  • お名前.comでネームサーバの設定

ご利用状況 > ドメイン一覧 > 指定のドメイン名クリック > ネームサーバの変更
その他 > 「その他のネームサーバーを使う」 > 4つレコードを記載して確認・保存

お名前.com Navi - Google Chrome 2020-09-16 21.02.58.png

無題 - ペイント 2020-09-16 21.04.24.png

無題 - ペイント 2020-09-16 21.05.47.png

お名前.com Navi - Google Chrome 2020-09-16 21.06.43.png

これで、「DNSの管理はRoute53で行います」という設定が出来ました。

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