- 投稿日:2020-09-21T23:17:22+09:00
Ruby×Sinatraで作ったSlackBotをAWS Lambdaで動かしてみる
本記事で目差す構成
① Slackで特定のアクションを実行する。(※今回はスラッシュコマンド)
② API Gatewayを介してLambdaを起動。
③ Lambdaに配置した関数を実行し情報を返す。↑動作イメージとしてはこんな感じ。
今回はとある地域の現在気温を返してくれる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 LambdaSlackBotを作成
まず、肝心のSlackBotを作成していく。
ディレクトリを作成
$ mkdir slack-bot-on-aws-lambda $ cd slack-bot-on-aws-lambdaRubyのバージョンを指定
# 2.5系なら何でもOK $ rbenv local 2.5.1Sinatraをインストール
$ 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.rbrequire '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=4567Sinatraはデフォルトだとポート番号「4567」で動くため、「localhost:4567」にアクセス。
「Hello World!」と表示されれば成功。
天気情報を返すプログラムを実装
今回作るSlackBotの主な機能である天気情報を返すプログラムを実装していく。
OpenWeatherのAPIキーを取得
上記サイトに会員登録し、APIキーを取得。
英語で書かれたサービスだが、ある程度は直感的に操作できるので詳しい説明は省略。どうしてもわからなかったらググればいくらでも記事が出てくるはず。
各種Gemをインストール
この先の処理を行う上で必要なGemがいくつかあるため、このタイミングで一気にインストールしておく。
./Gemfilegem 'faraday' gem 'rack' gem 'rack-contrib' gem 'rubysl-base64' gem 'slack-ruby-bot'「bundle install」も忘れずに。
$ bundle install --path vendor/bundlesrc/weather.rbを作成
$ mkdir src $ touch src/weather.rbsrc/weather.rbを作成し、次のように記述。
./src/weather.rbrequire '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 endmain.rbを編集
./main.rbrequire '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 endSlackBotのトークンを取得する方法については次の記事を参照。
参照: ワークスペースで利用するボットの作成
参照: API トークンの生成と再生成実際に動作確認
SlackBotをスラッシュコマンドで呼び出すためにいくつか設定しなければならない事がある。
https://api.slack.com/apps/
↑のURLにアクセスし、該当のBotを選択。
左サイドメニューに「Slash Commands」という項目があるので選択し、「Create New Command」をクリック。
各項目を入力していく。
- 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」をクリック。
スラッシュコマンドの作成が終わったら、SlackBotを追加したチャンネルで「/nerima」と打ち込んでみる。上手くいけば画像のようにSlackBotから返答が来る。(設定で画像や名前を変えたりする事も可能。)
何か不具合があった場合はターミナルにログが出力されているはずなので、適宜デバッグ。AWS Lambdaにデプロイ
正常に動作確認できたら、いよいよAWS Lambdaで本番稼働させる。
AWS CLIをインストール
今回は「AWS CLI」と呼ばれるツールを使いながらデプロイしていくので、まだインストールできてないない場合はインストールしておく。
$ brew install awscliIAMユーザーを作成
デプロイ作業を行うためのIAMユーザーを作成していく。
まずは「IAM」→「ポリシー」→「ポリシーの作成」へと進み、JSONタブから以下の文を貼り付ける。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "apigateway:*", "cloudformation:*", "dynamodb:*", "events:*", "iam:*", "lambda:*", "logs:*", "route53:*", "s3:*" ], "Resource": [ "*" ] } ] }
適当にポリシー名や説明を記述し、「ポリシーの作成」をクリック。
次に「IAM」→「ユーザー」→「ユーザーの作成」へと進み、適当な名前を付けた後「プログラムによるアクセス」にチェックを入れて次へ進む。
「既存のポリシーを直接アタッチ」から先ほど作成した「MinimalDeployIAMPolicy」を選択し、次へ進む。(タグは任意でOK)最後に確認画面が表示されるので、問題無ければ「ユーザーの作成」をクリック。
すると「アクセスキー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.rurequire 'rack' require 'rack/contrib' require_relative './main' set :root, File.dirname(__FILE__) run Sinatra::Application./lambda.rbrequire '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 endtemplate.yamlAWSTemplateFormatVersion: '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-sampleS3バケットの作成
事前に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」と表示されればデプロイ成功。
「Lambda」→「関数」と進むと先ほどデプロイした内容が表示されるので、「API Gateway」内に記載されているAPIエンドポイントにアクセス。
./main.rbget '/' do 'This is SlackBot on AWS Lambda' endmain.rb内のget '/' リクエストの期待通り「This is SlackBot on AWS Lambda」が返ってくれば正常に動作していると判断してOK。
Slash CommandsのRequest URLを変更
再びSlackBotの設定ページにアクセスし、「Slash Commands」からRequest URLを先ほど作成されたエンドポイントに変更する。(https://********.execute-api.ap-northeast-1.amazonaws.com/Prod/webhook」)
最後にもう一度、Slackチャンネルで「/nerima」と打ち込み、ちゃんとレスポンスが返ってくればめでたしめでたし。
もし上手く行かなかった場合はCloudWatchにログが出力されているはずなので、適宜デバッグ。あとがき
お疲れ様でした!
今回、簡単なSlackBotをLambdaで動かすというテーマでAWS Lambdaに触れてみました。
大した機能は実装できていないのでLamdaの素晴らしさを全て実感というわけにはいきませんでしたが、上手く使えばかなり便利なサービスだと素人ながら感じています。難しい操作はしていないため、基本的には手順通りに進めていけば動くはずですが、もし詰まるところがあった場合はコメント欄などで指摘していただけると嬉しいです。
- 投稿日:2020-09-21T22:07:22+09:00
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.pdfELB概要
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の全ての証明書が利用可能
- 投稿日:2020-09-21T21:58:50+09:00
AWS Glue で pg8000 driver を使って PostgreSQL の RDS インスタンスに接続する
Overview
AWS Glue のスクリプトで、DynamicFrameWriter を使ってできることより、もう少し色々細かいことをやりたかったので、PostgreSQL の RDS インスタンスに Python のライブラリで接続したかった。
その手順メモ。
なお、Glue では pure-Python なライブラリしか使えない。例えば pandas のような C library は未サポート。なので pg8000 を使う。
Procedure
手順
- pg8000, scramp (依存で必要) の tar ファイルをダウンロード
- tar を解凍して zip を作る
- zip を s3 に置く
- 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 のパスを入力
以下に入力。
複数あるのでカンマで区切る。
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 で行った設定をすればいける。
参考
- 投稿日:2020-09-21T21:18:52+09:00
【axios+SAM+API Gateway】apiを叩こうとしたらめちゃんこハマった件 (1/3)`SAM`を利用して、`API Gateway`及び`lambda`を構築
はじめに
APIの経験がないから、試してみたい
→axios
というのがいいらしいコーディングに注力したい(楽をしたい)
→AWS API Gateway
を使おうどうせなら構成はコード管理したい
→SAM
を使おうフロント及びバックエンド両方とも処理部分はとてもシンプルなものにしたのですが、
それでも「SAM
ってどうやるの?」「GET
メソッドがローカルから叩けない…」「GET
が
できたと思ったら、今度はPOST
が叩けない…」等々でだいぶハマりました。本稿では「ローカルから
GET
及びPOST
メソッドのAPI
を叩く」までを振り返り、
私が躓いたところをまとめようと思います。※開発環境はMacです。
やったこと
今回私は以下のような流れで進めました。
1.SAM
を利用して、API Gateway
及びlambda
を構築
1. ローカルから先程作成したAPI Gateway
のGET
メソッドを叩く
1. ローカルから先程作成したAPI Gateway
のPOST
メソッドを叩く今回は各ステップの内容について計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
が作成されます。
では今回作成した
API
をcurl
を利用して叩いてみたいと思います。$ curl https://{メソッドのURL}/Prod/hello {"message": "hello world!"}確かにメッセージが返ってきたことが確認できました。
終わりに
今回はSAMを使ったAPI環境の構築を行いました。次回はlocalhostを立てて、そこからAPIを叩いた際に直面した問題について触れたいと思います。
- 投稿日:2020-09-21T21:18:52+09:00
【axios+SAM+API Gateway】localhostからapiを叩けるようになるために苦労した話 (1/3)SAMを利用して、API Gateway及びlambdaを構築
はじめに
APIの経験がないから、試してみたい
→axios
というのがいいらしいコーディングに注力したい(楽をしたい)
→AWS API Gateway
を使おうどうせなら構成はコード管理したい
→SAM
を使おうフロント及びバックエンド両方とも処理部分はとてもシンプルなものにしたのですが、
それでも「SAM
ってどうやるの?」「GET
メソッドがローカルから叩けない…」「GET
が
できたと思ったら、今度はPOST
が叩けない…」等々でだいぶハマりました。本稿では「ローカルから
GET
及びPOST
メソッドのAPI
を叩く」までを振り返り、
私が躓いたところをまとめようと思います。※開発環境はMacです。
やったこと
今回私は以下のような流れで進めました。
1.SAM
を利用して、API Gateway
及びlambda
を構築
1. ローカルから先程作成したAPI Gateway
のGET
メソッドを叩く
1. ローカルから先程作成したAPI Gateway
のPOST
メソッドを叩く今回は各ステップの内容について計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
が作成されます。
では今回作成した
API
をcurl
を利用して叩いてみたいと思います。$ curl https://{メソッドのURL}/Prod/hello {"message": "hello world!"}確かにメッセージが返ってきたことが確認できました。
終わりに
今回はSAMを使ったAPI環境の構築を行いました。次回はlocalhostを立てて、そこからAPIを叩いた際に直面した問題について触れたいと思います。
- 投稿日:2020-09-21T20:34:10+09:00
【非エンジニアでも合格】ゼロから始める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. 教材
AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト(旧試験【SAA-C01版】対応版)
出題範囲の全体像の把握と、基礎固めのために使用。
旧試験対応版なので不要な部分もありそうですが、大した量でもないのでその辺りは気にせず全部読んでOK
この記事を書いている2020年9月の段階では、新試験【SAA-C02版】に対応した書籍はいずれの出版社からも出てなさそうなので、わざわざ買う必要はなさそう。
あると便利なので周りに持っている人がいれば借りるといいんじゃないでしょうか。これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(SAA-C02試験対応版)
上記の書籍と同様に、出題範囲の全体像の把握と、基礎固めのために使用。
別途BlackBeltで補う必要はあるが、この講座の範囲を理解すれば試験合格は可能です。
各サービスのハンズオンや本試験に近い難易度の模擬試験もついているのでとりあえず購入しましょう。【SAA-C02版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)
たくさん問題があります。すごい!BlackBelt(AWS サービス別資料)
Udemyや書籍では基礎的な部分までしか学べないため、足りない部分を補うために使用。
すべてを読む必要はないので、まずは主要なサービスから手を付けましょう。主要なサービス一覧
2020年9月時点の内容となります。
ちょくちょく更新されているので、最新のものを確認してください。
これ以外のサービスについては、気になるものがあれば適時読む感じでいいと思います。
※クラスメソッドさんの2回受験して得たAWS認定ソリューションアーキテクト(SAA)の教訓
を参考にしています。
Compute
Storage
Databases
Networking & Content Delivery
Management & Governance
Security, Identity & Compliance
4. 学習方法
学習方針
個人的には資格試験はあくまで網羅的に知識をインプットすることが目的なので、合格することを第一に考えた学習方法となっています。
実務で必要なら合格した後にじっくり触ればOKという考えです。学習の流れ
試験範囲全体の基礎を学び、後はひたすら模擬試験を解く。それだけ
基礎知識のインプット
これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座(SAA-C02試験対応版)を一通りやります。
動画時間だけで26時間+ハンズオンをまでしっかりやるとかなりの時間が必要になり、最初の内容はどうせ忘れてしまうので、サラッと流す感じで大丈夫です。
私はすべてのハンズオンを行いましたが、やらなくてもいいと思います。
書籍を用意できる方は、Udemyでやった部分を書籍で復習すると、お互いを補完できていい感じです。模擬試験
Udemyの模擬試験が8回分あるので、ひたすら回しながら足りない知識をBlackBeltやGoogleで補完していきます。
答えを暗記するのではなく、なぜそうなるのかを理解するようにしましょう。
私の場合はすべての模試を3周解いて、正答率が90%~95%くらいでした。学習の際に意識すること
試験合格のために重要なのは
主要なサービスの内容と、それらをどのように組み合わせて設計をするか(ベストプラクティス)をしっかり覚える
ことです。
これだけで合格ラインの7割に十分届きます。そのため、試験の合格のみを考えるなら【SAA-C02版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)の高難度の模擬試験に1,2回しかでてこないような単語、サービスは覚えなくても問題ありません。
多少の偏りはあると思いますが、私が受けた時は主要なサービスについての問題しか出ませんでした。覚えるべきサービスに優先順位をつけるとこんな感じです。
- 主要なサービス ← 必須
- 模擬試験でよく見るサービス ← 余裕があれば
- 模擬試験全体を通して1,2回しか出てこないサービス ← 不要
5. 本試験
この手の試験全般に言えることですが、試験問題の日本語が不自由すぎてつらいです。
試験時間は130分で、当初の予定では60分くらいで1周できると思っていたのですが、
一通り問題を解き終わった時点で残り20分。。。
半分くらい見直したところで時間切れとなりました。
正直手ごたえはなかったのですが、結果は・・・合格!
試験が終わって3時間後くらいには詳細な点数が出ていて、[798/1000]でした。
合格ラインが720点なのでそこそこ余裕があったのではないでしょうか。6. あとがき
試験の難易度としては簡単とはいいませんが、
すべて選択問題なのでそこまで気負う必要もありません。
知識0からの方でも一発合格は十分可能だと思います!AWS Certified Solutions Architect - Associate【SAA-C02】の勉強を通して、
AWSについての基礎知識、インフラとはどういったものなのかがなんとなくわかった気がします。
ただ、あくまで知識がついただけなので、今後は実際に自分で書いたコードをAWSで実装しながら、
もっと理解を深めていきたいです。最後にTwitterもやっていますので、ぜひフォローしていただけると嬉しいです!
- 投稿日:2020-09-21T19:49:55+09:00
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ほどではないものの)普通にやると結構時間がかかる
- 投稿日:2020-09-21T18:40:04+09:00
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.yamlrepositories: - 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.yamlapiVersion: 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.yamlGitLab 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.yamlglobal: 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: falseGitLabのデプロイ
次のコマンドでGitLabをデプロイします。
$ helm install ¥ --name test-gitlab ¥ --namespace test ¥ --values ./gitlab/my-values.yaml ¥ ./gitlab確認
/gitlab
にアクセスして問題ないことが確認できます。GitLab RunnerをデプロイしておけばGitLab CIを実行することも可能です。
まとめ
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
- 投稿日:2020-09-21T18:39:07+09:00
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ロールなどのセキュリティ周りを正しく設定できるように色々調べたいと思います。
- 投稿日:2020-09-21T12:38:31+09:00
Amazon Route 53
Amazon Route53とは
可用性と拡張性に優れた、ドメイン管理機能と権威DNSを持つサービスです。
DNS情報を参照するだけではなく、ネットワークトラフィックのルーティングや接続先の状況に応じた接続先の変更などが可能です。ちなみになぜ53かと言うとDNSが使用するポート番号が53番だからだそうです。
ドメイン管理
Route53では新規ドメインの取得や更新などの行うことが出来ます。
◯メリット
・ドメイン取得からドメイン登録設定がとても簡単
・ドメイン使用料が通常のAWS利用料の請求に含まれる一貫管理が可能△デメリット
・有料
・ドメインの維持コストや取得コスト自体を比較するとお名前.com等の方が安くすむ事が多い。ホストゾーン
管理可能なレコード情報の集合
レコード情報とは192.168.100.100
はexample.com
ですよ〜といったドメインをIPアドレスに変換する為の情報◯パブリックホストゾーンとプライベートホストゾーン
■パブリックホストゾーン
インターネット上に公開されたDNSにメインレコードを管理する。
example.com
とそのサブドメイン(hoge.example.com
やfuga.example.com
)のトラフィックをインターネット でルーティングする方法についての情報を保持するコンテナ。
■プライベートホストゾーン
VPCで構成されたプライベートネットワークにおいてDNSドメインのレコードを管理する。(VPC内のサーバー間で名前解決したい時とか)
レコードタイプ
◯Aliasレコード
Route53の特徴的なレコードとして
Aliasレコード
があり、レコード情報に登録する値として、CloudFront
,ELB
,S3
などのAWSリソースFQDNを指定できる。
CNAME
でも同じような登録は可能ですが、大きな違いとして最上位ドメインのZoneApex
にも登録出来るということが挙げられます。
DNSクエリ回数が減りパフォーマンス向上が可能です。
タイプ 説明 機能 A IPv4 アドレス hoge.example.com
は54.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 のプライベートホストゾーンを使ってみた
- 投稿日:2020-09-21T12:00:43+09:00
AWS SSOにAPIが追加されてCLIやCloudFormationで操作できるようになったよ
はじめに
2020/9/10 ついに AWS Single Sign-On に AWS SSO API (sso-admin) が追加され、
AWS CLI/SDK や CloudFormation による操作もサポートされました。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 を確認することもできます。
以降では 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.PermissionSetArnTargetType は
AWS_ACCOUNT
のみが指定可能で
TargetId には割り当てを行う AWS アカウントIDを指定します。TargetId: '123456789012' TargetType: AWS_ACCOUNTPrincipalType には
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以上です。
参考になれば幸いです。
- 投稿日:2020-09-21T11:57:58+09:00
【Bitcoinを自動売買】AWSのDockerで運用してみた話、外出先のAppleWatchから1TAPでON/OFF&Line通知付き
autocoin2
BitCoinのFX自動売買プログラム。
BitflyerのAPIを利用して、node.jsにて仮想通貨トレードを自動化しました。
寝てる時、トイレ中、24時間中、お金が勝手に増えてくれたら、どんなに素敵だろう。。
楽して自動的に儲かりたい!そんなダメ人間モチベーションで作ってみました。
いきなり結論ですが、、、残念ながら儲かりません
むしろ、減っています。。ですが、チューニングしたら、ひょっとしたら儲かり出すかもしれません。
(損害を受けても当方は一切責任はありません。)
あくまで、自己責任でお願いします!特徴
- 売り・買いポジション両方対応
- 複数アルゴリズムによる重み付け売買判断
- MongoDBによる売買履歴の保存
- 取引開始をLine通知
- 損得金額の閾値を超えたら、Lineにて通知
- 一定の日数が経過したら、ポジションを自動で手放す機能
- 日付変更30分前には、新たなポジション取得を抑制する機能
- Apple Home連携で外出先でもiphoneから1タップでON/OFF
- プログラム稼働中でも、並行して通常の人的トレードも可能
システム概要
使用技術
- 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できるようにしました。
- AWS EC2インスタンスにDockerを展開
- 自宅のRaspberry Piにhomebridge展開し、実行のshellファイルを紐付ける
詳しくは以下を参考ください。
公式homebridgePhilips_HueをAPI連動!〜ラズパイをHomeKit化する
IntelliJで取引DBを見る
MongoDBに保存した取引内容ですが、閲覧はIDEの利用をおすすめします。
直接MongoDBからの閲覧は、json形式のためかなり辛いです。
IntelliJなどのIDEでしたら、いい感じに見やすくしてくれます。
IntelliJの設定方法は、過去記事を参照ください。
IntelliJからAWSを操作する設定方法まとめ注意点
最初のDocker立ち上げ
MongoDBが書き込み可能になるのに時間かかります。
volumeディレクトリのcontainer_dataを作成するためです。
余裕持って初回起動して、しばらくしても書き込みされなければ、再度Dockerを立ち上げ直してください。
私の場合ですが、2回目以降は問題なく稼働しています。Docker Volumeのcontainer_dataはroot権限
作成されるvolumeディレクトリのcontainer_dataはroot権限で作られます。削除したい際はsudoを付けてください。
私のうっかり経験ですが、Dockerを再ビルドする際に、このディレクトリ権限に気付かずエラーになって少しハマりました。終わりに
楽して不労所得の夢は実現しません
しつこいですが、必ず儲かるわけではないですし、自己責任でお願いします。ひょっとしたら、
私のアルゴリズムや、パラメーターがイマイチナだけで、誰かが追加したアルゴリズムにより儲かりだすかもしれません。。。そんな時は、こっそりと教えて下さいね
それでは、よろしければホビーとしてほどほどに楽しんでみてください。
- 投稿日:2020-09-21T11:47:46+09:00
理想を追い求めたCI/CDパイプラインをTerraformで実装するためのポイント(production環境編)
はじめに
前回の記事の続編。
前回は、クロスアカウントで、ビルド環境でビルドしたコンテナイメージをstage環境にコピーする部分だったが、今回の範囲は、それをproduction環境にコピーする部分だ。前提知識としては、前回のパイプラインを理解できていること。
新しい概念は出てこないので、サクサク書いていく。プロバイダの設定は前回と同じと考えてほしい。
本来だったらプロバイダを、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
の環境変数も参照可能になる。
- 投稿日:2020-09-21T11:44:06+09:00
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 とシークレットアクセスキーを設定します。
AWS CLIのインストール
インストール手順は以下のURLを参照してください。
参考:https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html設定
以下のURLを参考にアクセスキー ID とシークレットアクセスキーを設定します。
参考:https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html.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.csusing 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関数が表示されます。
AWS Lambda上でテスト
AWS LambdaコンソールでデプロイしたLambda関数を選択し、「テストイベントの設定」をクリックします。
「イベント名」欄に任意の名称を入力し、その下の編集エリアに引数に渡す情報をJsonで入力して保存ボタンをクリックします。
先ほど「イベント名」欄に入力した名前を選択してテストボタンをクリックすると、実行結果欄にFunction.csのFunction.FunctionHandler()メソッドの戻り値が表示されます。
Visual Studio for Macでソリューション化
Visual Studio for Macでコーディングやデバッグ実行をしたいのでソリューションを作成します。
まずは、空のソリューションを作成します。プロジェクトの作成で作成されたディレクトリ構成に合わせてソリューションの場所を指定します。ディレクトリ構成はお好きにどうぞ。
ソリューションウィンドウでソリューション名を右クリック→[追加]→[既存のプロジェクト]を選択し、Lambdaプロジェクトとそのテストプロジェクトを追加します。
テストの実行
テストプロジェクトをスタートアッププロジェクトに設定します。
単体テストウィンドウを表示し、テストメソッドを右クリック→[テストのデバッグ]をクリックすると、テストメソッドをデバッグ実行できます。
- 投稿日:2020-09-21T11:23:52+09:00
意外と簡単!EC2上のWebアプリを独自ドメイン化&SSL化
さようならFirefox Send。あなたは私のT4gインスタンスの中で永遠に生き続けます!!
でEC2上にWebアプリケーションを構築しました。
欲が出て独自ドメイン化&SSL化をしてみました。ドメインの取得
freenomで
.tk
、.ml
、.ga
、.cf
、.gq
ドメインが無料で取得できます。
欲しい物を探してドメインを取得します。
Route53の設定(ホストゾーンの設定)
ホストゾーンを追加します。取得したドメイン名をパブリックホストゾーンとして登録します。
登録すると、NSレコードが作成されます。ここで4つのDNSサーバーのドメインが自動で指定されます。
ここで指定されたドメインをfreenomに設定します。
Services -> My Domains
Manage Domains
Management Tools -> Nameservers
これでRoute53でドメインが管理されます。反映までに結構時間がかかります。
ACM(AWS Certificate Manager)の設定
SSL証明書を発行します。
バージニア北部(us-east-1)リージョンを選択します。ドメインは
*.
を先頭に付けてワイルドカード証明書としましょう
次々進み、検証のところで、
Route 53でのレコードを作成
ボタンを押します。
しばらくすると証明書が発行済となります。これもちょっと時間がかかります。
CloudFrontの設定
Create Distributionから作成を進めます。
WebのところのGet Startedを押します。
Origin Domain Name
にEC2のパブリック IPv4 DNSを入力します。
Origin ID
は自動で入力されますが、うまく入力されるときとされない時があります。。何度もOrigin Domain Name
を入れたり消したりしてるとそのうちうまっくいくと思います。
HTTP Port
とHTTPS Port
をEC2で起動しているWebアプリに合わせます。(今回はHTTPが1443ポート)
Viewer Protocol Policy
をRedirect HTTP to HTTPS
に、Allowed HTTP Methods
をGET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
に変更します。
Origin Request Policy
をManaged-AllViewer
にします。
Alternate Domain Names(CNAMEs)
に使用するドメイン名(moritalous.ga)を入力し、SSL Certificate
をCustom SSL Certificate
にしてACMで作成した証明書を選択します。
Route53の設定(サブドメインの設定)
サブドメインの設定を行います。
ホストゾーンの選択 -> レコードを作成
シンプルルーティング
を選択します。
レコード名を入力し、
値/トラフィックのルーティング先
でCloudFrontディストリビューションへのエイリアス
を選択し、作成済みのディストリビューションを選択します。
設定は以上です。
動作確認
CloudFront発行のURL(HTTPS)
CloudFront発行のURL(HTTP)
HTTPSにリダイレクトされ表示。OK
Route53発行のURL(HTTPS)
Route53発行のURL(HTTP)
HTTPSにリダイレクトされ表示。OK
うまくいきました。
- 投稿日:2020-09-21T11:18:21+09:00
【凡人向け】AWS認定ソリューションアーキテクト・アソシエイト 合格体験記
▶この記事の対象者◀
この記事は凡人向けです!
筆者のスペック
▶業界歴
2年目
▶資格
なし
▶経験のあるサービス
RDSAurora DynamoDB Redshift Lambda Glue CloudWatch
▶学習期間
100時間程度(週5時間・半年ぐらい)
受験のきっかけ
業務の中でAWSを利用していましたが、AWSがまったくわからなかったので学習を始めました。
学習するならついでに資格取得も挑戦してみようと思って受験しました。結果
学習方法
▶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ライフを送りましょう!
- 投稿日:2020-09-21T08:16:07+09:00
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.htmlAWS Step Functionsを使う
次に、
データを書き込むLambdaからデータを削除するLambdaを呼び出す
を検討します。
調べた結果、AWS Step Functionsを使うことで実現できそう。実装してみます。データを書き込むLambda関数の作成
ランタイムはPython3.8、DynamoDBとStepFunctionsへのアクセス権を付与します。
以下のLambda関数でDynamoDBへデータを書き込み、StateMachineを呼び出します。
userIdをパラメータとしてStateMachineに渡します。lambda_function.pyimport 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 } } }データを削除するLambda関数の作成
StateMachineから、以下のLambda関数を実行することでデータを削除します。
lambda_function.pyimport 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/
- 投稿日:2020-09-21T01:59:53+09:00
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ファイルに記入した環境変数とパスワードを囲むシングルクォートが全角だとエラーになる。(シングルクォートを含んだパスワードと見なされるため)
環境変数とパスワードを表示するコマンドを入力した時、パスワードと環境変数がシングルクォートで囲まれているかどうかで確認できる(半角だとシングルクォートが出ない)。まとめ
タイトルのようなエラー文が表示された時はパスワードが間違っているので要確認。
- 投稿日:2020-09-21T01:12:40+09:00
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へのサインアップ
アメリカ人かつ政府関係者向けのサービスなので個人で触ることはなさそう。以上。
- 投稿日:2020-09-21T00:44:45+09:00
冴えないAWS環境の育てかた α を育ててみた�<準備編>
はじめに
クラスメソッドさんの技術ブログ「Developers.io」にてこの様な記事が投稿されました
https://dev.classmethod.jp/articles/saenai-aws-1/この様な稀によく見るAWS環境を「Well Architected Framework」を意識しつつ、
- AWSアカウントの育てかた
- ネットワークの育てかた
- サーバーの育てかた
- データストアの育てかた
の4つに分けて育てていくという記事です。
今日から約1〜2ヶ月間、力試しという形で自分なりに+αでできることも加えつつ、このアーキテクチャに挑戦してみます!
準備編
今日は準備編ということで、元となるアーキテクチャを設計していこうと思います。
デプロイ対象のアプリケーションはフレームワークの練習用に作成した。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 httpdcomposerのインストール
$ curl -sS https://getcomposer.org/installer | php # パスを通す $ sudo mv composer.phar /usr/local/bin/composergitからアプリを持ってくる
$ sudo yum install git $ cd /var/www $ git clone https://github.com/mkoki0422/Laravel_keiziban.gitApacheの設定
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 httpdvendorディレクトリの作成
$ composer install.envファイルの作成
#.envファイルの作成 $ cp .env.example .env # .envのAPP_KEYの生成 $ php artisan key:generateRDS
サブネットグループ作成
RDSを構成する前にRDSを配置するサブネットグループを作成しましょう。
マネジメントコンソールの「サブネットグループ」から「DBサブネットグループの作成」に進みましょう。
注意 RDSはMultiAZ構成をしない場合でも、2つ以上のAZを指定しないといけません!データベース用セキュリティグループ
DBサーバーに適用するためのセキュリテイグループも事前に作成しておきます。
インバウンドルールのタイプを「MYSQL/Aurora」、ソースはEC2で適用しているセキュリティグループを選択する方法と、IPアドレスを選択する方法がありますが、今回はセキュリティグループをソースとして指定します。
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サーバーを複製します。
使ってなかったドメインがあったので、複数値ルーティングで2つのWEBサーバーをルーティングさせました。
明日の予定
今日は準備編ですので、とりあえずデプロイして、RDSに接続、Route53でルーティングさせるところまでやってみました。
明日は
- Route53 / ヘルスチェックの有効化
- CloudFront / ディストリビューションの作成
- CloudFront / 追加のメトリックを有効化
- CloudFront / アクセスログの取得
- CloudFront / 特定地域からのアクセスをブロック
- CloudFront / カスタムエラーレスポンスとしてSorry Pageを構成
- CloudFront / 監視
- ACM / サーバー証明書の作成
- ACM / CloudFrontへの関連付けと適切なセキュリティポリシーの選択
を進めていこうかと思いますー!
- 投稿日:2020-09-21T00:15:49+09:00
Route53でホストゾーンの作成
ドメインの取得
有名なのは、お名前.com。
定番の.com
や.jp
もあり、.work
だと1円のドメインもあります。
アカウント登録から、ドメイン取得まで10分もかからずに出来ました。また、無料ドメインだとfreenomがあります。
無料でドメインが取れますが、日本のサイトではないので少し見づらいかも?
こちらも簡単にドメイン取得ができます。Route53でホストゾーンの作成
- Route53でホストゾーンの作成
Route53のダッシュボードから
ホストゾーンの作成
に進みます。
- ホストゾーンの設定を記載
ドメイン名
と説明-オプション
を記載、
タイプは外部に公開するのでパブリックホストゾーン
を選択します。
選択が出来たら、ホストゾーンの作成
を押して、完了です。
ホストゾーンの内容設定画面に移行し、ホストゾーンの作成は完了です。
ネームサーバの設定
今回私はお名前.comでドメインを取得したので「ネームサーバの設定をRoute53でしましたよ」とお名前.comで設定しないといけないです。
- Route53でNSレコードの確認
黒で消してしまっているのですが、赤い枠の中に4つレコードがあります。
そちらをお名前.comで設定します。
- お名前.comでネームサーバの設定
ご利用状況 > ドメイン一覧 > 指定のドメイン名クリック > ネームサーバの変更
その他 > 「その他のネームサーバーを使う」 > 4つレコードを記載して確認・保存これで、「DNSの管理はRoute53で行います」という設定が出来ました。