20210325のAWSに関する記事は26件です。

Active Directory Connectorってなんだろ?

Active Directory Connectorってなんだろ?

AWSにはActive Directory Connectorというサービスがありますが、どんなサービスなんでしょうか?
ドキュメント等をながめて自分なりに概要をつかんでみました。

ドキュメントの前に

一応超ざっくりADについて勉強しておきましょう
アクティブディレクトリ (Active Directory)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典より

アクティブディレクトリ (Active Directory)とは

  • Windowsの元締めコンピュータさんによる縄張り制度のこと。
  • Windowsのサーバさんに搭載されている、ネットワークにつながっているパソコンとかをまとめて管理するための仕組み
  • 元締めコンピュータは、ネットワーク上で好き勝手やってる奴等を統括的に管理するのが使命
  • 元締めコンピュータに対する1回の認証で、いろいろなパソコンとやり取りができるようになる(シングルサインオン)
  • Active Directoryにおいては、元締めコンピュータを「ドメインコントローラ」、元締めコンピュータの持つ縄張りを「ドメイン」と呼ぶ
  • 元締めコンピュータ同士で業務提携をすることもできる

ドキュメント

ADについてざっくり分かったところでドキュメントを見てみましょう。
Active Directory Connector - AWS Directory Serviceより

Active Directory Connectorとは?

  • 略してAD Connector
  • クラウドの情報をキャッシュせずにディレクトリリクエストをオンプレミスの Microsoft Active Directory へリダイレクトするのに使用するディレクトリゲートウェイ
  • スモールとラージの 2 つのサイズがある
  • ユーザー制限や接続制限はない
  • 他の AWS アカウントと共有することはできない

メリット

  • エンドユーザーと IT 管理者は、既存の会社の認証情報を使用してAWSサービスにアクセスできる
  • 既存のセキュリティポリシーを一貫して適用することができる
  • 既存の RADIUS ベースの MFA インフラストラクチャと統合し、多要素認証を有効にして、ユーザーが AWS アプリケーションにアクセスするときに追加のセキュリティレイヤーを提供できる

急にハードルが上がった感がすごいです。
さすがに理解が追い付かないので、いったん別の記事をみてみましょう。

困ったときのDevelopersIO

機能の基本や実際の設定を勉強するにはクラスメソッドさんのDevelopersIOがおすすめです!
Active Directory Connector(AD Connector)を試してみた | DevelopersIOより

ドキュメント + α

  • AWS環境からオンプレ環境にあるドメインコントローラーに対する通信を中継するためのプロキシサービスである
  • 汎用プロキシではなく例えばWorkSpaces、WorkDocs、WorkMailといったAWSのサービス向けの専用プロキシである
  • 既存RADIUSサーバーと組み合わせて多要素認証を有効にできる

AWSの特定サービス ⇔ AD Connector ⇔ オンプレの元締めマシン
というかんじで、AWSとオンプレをつなぐような役割、つまりConnectorというわけですね。

利用するための前提条件

  • 少なくとも 2 つのサブネット。各サブネットはそれぞれ異なるアベイラビリティーゾーンに設置
  • VPC は、仮想プライベートネットワーク (VPN) 接続または AWS Direct Connect を通じて既存のネットワークに接続されている必要がある
  • VPC にはデフォルトのハードウェアテナンシーが必要

要は複数サブネットにわたる冗長構成である必要があるということです。 これは他のAWSサービスでも良くあるパターンですね。

  • Active Directory ドメインを持つ既存のネットワークに接続する必要がある
  • ドメインの機能レベルは Windows Server 2003 以上
  • EC2 インスタンスでホストされているドメインへの接続にも対応している

要は機能レベルWindows Server 2003以降のドメインコントローラーと疎通できる環境にある必要があるということです。

  • ユーザーおよびグループの読み取り * コンピュータのオブジェクトの作成 * コンピュータのドメインへの結合の権限が委任されている既存のディレクトリのサービスアカウントの認証情報が必要

AD Connector作成時に専用のドメインユーザーが必要になります。 権限をサービスアカウントに委任するに従い専用のグループとユーザーを用意します。

利用するにもおいそれと素人がすぐに始められるわけではなさそうでした。
特に既存のネットワークがあって、それとVPCをつないでおく必要があるみたいなので、個人でやるにはハードル高めな気がします。
逆に会社などですでに前提を満たしていればあとはコンソールでポチるだけってかんじでしょうか。

オンプレでの設定

このへんは正直全く分からないので、詳しくはブログの手順をご覧ください。
最終的にEC2をオンプレのADに参加させるという手順のようです。

なんかスッキリしないから

とりあえずADを触ったことがないので、まずは基本的な部分からやってみたいと思ったところ、良い記事を発見しました!
EC2からADサーバーを自分で作成し、WorkSpacesのディレクトリとして使ってみよう! - サーバーワークスエンジニアブログ

今回の記事ではやりませんが、近日中にやってみたいと思います。
初心者にも分かりやすく丁寧に図と説明があるので、ADをほぼ知らない僕でもできそうです!
気になる方はぜひやってみてください。
ざっくり理解のために冒頭だけ抜粋してみます。

Directoryとは

情報を収納するためのものを指します。データベースや電話帳などをイメージするといいでしょう。

Directoryサービスとは

Directoryに収納された情報を見つけやすくするためのサービスを指します。例えば、DNSサービスも該当します。IPアドレスと紐付いたドメイン名に関する情報がたくさんある中で、それをクライアントが見つけやすくしてくれるからです。

Active Directory(=AD)とは

Microsoft社がWindows2000から提供しているDirectoryサービスとなります。Windows server 2008以降では「Active Directory Domain Service(AD DS)」と呼ばれています。

冒頭で紹介した内容を少し専門的に言い換えたようなかんじですが、言ってることは変わらないと思います。
元締めマシンがドメインどもを管理する仕組みでしたね。

まとめ

今回はAWSのActive Directory Connectorについて調べてみました。
と言ってもやはりADに関する知識がないとハードルが高かったです。でも、AWSの特定サービス ⇔ AD Connector ⇔ オンプレの元締めマシンという位置づけだけでもわかったので収穫ありです。うまく使えばAWSで改めて権限管理をしなくても、既存のAD情報でAWSの特定サービスへのサインオンができるというメリットもありましたね。
詳しいことはとりあえずADについてやってみてから出直したいと思います!

こんな記事でもどなたかの参考になれば幸いです。
ご覧頂きありがとうございました!

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

【Rails&heroku】MySQLで出来てた「あいまい検索」がPostgreSQLで出来ない場合にやること

AWSでコード完成、いざherokuへデプロイ!・・・したけどエラー。
調べてみると、あいまい検索の部分がおかしいらしい。

【ページ内リンク】

0.環境
1.ソースコード
2.そもそもあいまい検索部分が原因だと分かった経緯
3.解決策

0.環境

・AWS
・heroku/7.48.0 linux-x64 node-v12.16.2
・Rails 5.2.4.5
・ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
・MySQL 5.7.31
・PostgreSQL 9.2.24

1.ソースコード

Example.rb
@teams = Team.where("#{key} like ?", "%#{value}%")

・いたって普通のあいまい検索。keyがキー、valueが値。
【例:key = "id"、`value = "(検索用に入力した値)"】
・MySQLだと上手くいったのに、PostgreSQLだとエラーが生じた。

※ちなみに、下記に2通り完全一致検索方法を書いたが、方法1はどちらでも作動し、方法2PostgreSQLでは作動しなかった。

Example2.rb
@teams = Team.where("#{key} = #{value}")   #方法1
@teams = Team.where("#{key} like ?", "#{value}")   #方法2

たぶん「like」が悪そう。

ページ内リンクへ戻る

2.そもそもあいまい検索部分が原因だと分かった経緯

$ heroku logs -t

heroku logs --tailの略。これを打てば、どこでエラーが出たか一発で分かる。

ページ内リンクへ戻る

3.解決策

調べてみると、keyvalueの型が違うのが原因らしい。

Example.rb
@teams = Team.where("cast(#{key} as text) like ?", "%#{value}%")

PostgreSQLなら、これで良い。cast、RubyやRailsというよりSQL寄り?
でもMySQLだとこれはエラーとなった。絶対何か他にいい方法あるよなぁ。

ページ内リンクへ戻る

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

Cloud9上へzipをアップロードして、ファイルを解凍して環境構築しなおしてみた

実施背景

「no space left on device」エラーが発生してしまい、ERBボリュームを増やそうとするも、重すぎてターミナルが動かなかったため、新たに環境を作り直すべく、今まで作っていたファイルをzipで移動させようと考えました。

本来であれば、gitからpullする方法が一般的かと思いますが、まだpushしていない資材があったため、今回はzipファイルをダウンロードしたのち、新たに構築したcloud9の環境にアップロードして解凍する手法を取りました。

実施

1.Cloud9環境構築
まず、cloud9上の環境を新たに構築します。
cloud9で環境構築した際の工程は下記に書きました。

Railsチュートリアル6.0に沿って、AWSのCloud9上でRailsの開発環境作ってみた
https://qiita.com/GalaxyNeko/items/c9521c57e03f63e14f89

2.zipファイルをアップロード&解凍
cloud9上にzipファイルをアップロードします。
zupファイルのアップロード完了後に、下記のコマンドをターミナルに打ち込んで解凍します。

unzip xxxx.zip

そのあと、筆者はRailsのファイルを上げていたので、bundle updateやdb:migrateなどを打ち込んで環境を整えて、cloud9上でzipファイルを解凍し、環境構築できました。

参考

[Linux]ファイルの圧縮、解凍方法
https://qiita.com/supersaiakujin/items/c6b54e9add21d375161f

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

AWS VPC + EC2 でWeb Server・DB Serverを構築する②

こんにちは!
TWクライアントチームで、フロントエンドエンジニアをやっていますTKと申します。普段はReact + TypeScriptな環境で、webアプリの開発を行っています。

この記事は、前回の記事の続きになります。

前回は、EC2インスタンスを作成しsshで接続するまで行いました。今回は、前回作ったEC2インスタンスにnginxをインストールしWeb Serverとして機能するようにします。

筆者は、AWS初心者かつインフラ初心者ですので、暖かく見守っていただけると幸いです。アドバイスやコメント等もお待ちしております!

手順

  1. nginxインストール・起動
  2. index.htmlを配置する

1. nginxインストール・起動

nginxをインストールします。
Amazon Linux 2にnginxをインストールする際には、amazon-linux-extrasからインストールするようです。
下記コマンドをEC2インスタンスの中に入って実行します。(EC2インスタンスへ入るには、AWSコンソールから対象インスタンスを選択し「接続」ボタンを押して入ることもできますし、前回記述したsshで接続して操作することもできます。)

$ amazon-linux-extras

すると、nginx1というパッケージがあることが確認できます。
では、nginx1をインストールしてみます。

$ sudo amazon-linux-extras install -y nginx1

正常にインストールがされているか確認します。

$ nginx -v

バージョンが返ってくれば、OKです!
nginxを起動します。

$ sudo systemctl start nginx

起動しているか確認します。

$ systemctl status nginx

active(running)と表記があれば起動しています。
EC2インスタンスが起動したら、自動でnginxを起動するようにしておきます。

$ sudo systemctl enable nginx

nginxはデフォルトでは、80番ポートでlistenしているので80番を開きます。
AWSコンソールのEC2ページから、インスタンスにアタッチしたセキュリティグループを選択します。

  1. サイドバーにある【セキュリティグループ】を選択し、アタッチしたセキュリティグループにチェックを付ける
  2. 下部に詳細が表示されるため、【インバウンドルール】タブを選択し、【インバウンドルールを編集】をクリックする
  3. 【ルールを追加】をクリック後、タイプに HTTP / ソースに 0.0.0.0/0 を入力する => 今回ソースは全公開にしております。IP制限するなら、0.0.0.0/0ではないIPを入力してください。

ここまで設定したらブラウザで、「EC2インスタンスのパブリックIP」にアクセスしたら、nginxのデフォルトページが表示されるはずです。

2. index.htmlを配置する

次にindex.htmlを配置し、ページを公開していきます。
nginxのドキュメントルートを調べます。

$ grep "root" -r /etc/nginx/ | grep "html"

root /usr/share/nginx/html;と出ましたので、/usr/share/nginx/html以下にindex.htmlを配置すれば、公開できることがわかりました。
用意したindex.htmlをサーバにsshで送ります。

scp [コピーするファイル名] [接続先のユーザ名@接続先のホスト名:ディレクトリ]

複数ある場合、空白の後に続けて記載します。
ディレクトリがある場合 -r を付けます。
ディレクトリの中身を全てコピーするなら、*が便利です。

scp -r ./* [接続先のユーザ名@接続先のホスト名:ディレクトリ]

scpを実行したときに、Permission deniedが発生しました。
コピー先の権限の問題だと思います。
下記コマンドを実行して、パーミッションを変更すると消えました。

chmod 777 /usr/share/nginx/html

上記が完了したらブラウザで、「EC2インスタンスのパブリックIP」にアクセスしたら、index.htmlの内容が表示されているはずです。 

おまけ1(リバースプロキシ編)

nginxをリバースプロキシとして動作させる場合、nginx.confファイルをいじります。
Amazon Linux2では、/etc/nginx/の下にnginxの設定ファイル等配置されます。
いじる前に、念の為バックアップをとります。

$ cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bakup

自分はvimが使えないので、nanoを使用して設定を変えていきます。(すみません。nanoは分かりやすくてありがたい!)

$ nano /etc/nginx/nginx.conf

nginx.confファイルのserverの下にlocationがあります。
そこを

location / {
    proxy_pass [リダイレクト先];
}

このように変更すると、80番ポートに送られてきたリクエストを転送できます。

自分は、Express(node.js)で作成したApiを配置してみました。
4000番ポートでlistenしていたので

location / {
    proxy_pass http://127.0.0.1:4000;
}

に設定しました。
ブラウザから確認したところ、動作が確認できました。

おまけ2 (dockerを入れる)

Web Serverとは直接関係ありませんが、EC2にdockerを入れる必要がありましたので、ついでに記載しておこうと思います。

dockerもamazon-linux-extrasコマンドから、インストールできるようです。
念の為、dockerがあるか確認する。

$ amazon-linux-extras

dockerをインストールします。

$ sudo amazon-linux-extras install docker

sudoをつけずにdockerを実行するために、ec2-userをdokcerグループに追加します。
これで、sudoをつけずにdockerが実行できます。

$ sudo usermod -a -G docker ec2-user

dockerが使えるか確認します。

$ docker info

上記を実行し、情報が帰ってきたら成功です!
あとは、Dockerfileなりdocker-compose.ymlなりを用意して、dockerを使用してください。

まとめ

今回は、短いですがここまでにします。前回用意したEC2インスタンスにnginxをインストールし、Web Serverを構築しました。nginxの設定は、他にもたくさんあります。実運用する際は、詳細に設定を入れるべきだと思います。インフラ初心者ですので、これから調べつつ設定を増やしていきたいです!

ここまで読んでくださった方、ありがとうございます!
次回は、DB Serverを構築していきたいと思います。
冒頭に申しました通り、AWS初心者かつインフラ初心者ですのでアドバイスをお待ちしております!

【参考文献】
https://qiita.com/sakkuntyo/items/807f25f9eb13525eebef
https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04-ja
https://qiita.com/shisama/items/5f4c4fa768642aad9e06
https://hacknote.jp/archives/49429/
https://qiita.com/pugiemonn/items/3c80522f477bbbfa1302
https://hacknote.jp/archives/49429/
http://mogile.web.fc2.com/nginx/admin-guide/reverse-proxy.html
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html

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

Google Cloud Functionsから地域制限をかけたAmazon CloudFrontへアクセスすると拒否される件について

はじめに

どうもみなさん、こんにちは。いやー、最近こんな現象が出てきて困ったんすよね。

現象

日本リージョン(東京/大阪)にデプロイしたGoogle Cloud Functionsから、日本からの受付のみ許可するように地域制限をかけたAmazon CloudFrontにアクセスをしました。期待はもちろん成功なんですが、、、
error
ジャジャーン!はい、キタコレ、拒否!!、、、きっと仲が良くないん(ry

CloudFrontってどうやって地理を判定しているの?

Amazon CloudFrontデベロッパーガイド」を見ると、サードパーティのGeoIPデータベースを使っていますよってことです。あー、なるほど正確性は「99.8%」なのか。
georestrictions-cloudfront-001.png
てか、サードパーティのGeoIPデータベースってどこよ?と思ったら「Amazon CloudFront API Reference」の方には MaxMind GeoIP databases って明記されておりました。
georestrictions-cloudfront-002.png
なるほど、じゃあ、ここのデータベースにGoogle Cloud Functionsで利用されているIPアドレス範囲が入ってくればいいのか、、、まぁAWSを疑ってたわけだけどAWSは全然悪くないのね。

余談、どんなコード書いたの?

「Cloud Functionsでどんなコード書いて試したの?」って聞かれたので「ん、こんな感じの雑なので試しましたよ」って答えておく。(でも、ほんとはググってほしい)

main.py
def hello_world(request):
    import requests
    response = requests.get('http://XXXXXXXXXXXXX/index.html')
    return response.text
requirements.txt
requests

最後に

AWSさんから見るとやっぱGCPって外国扱いなんかな、と思ったらAWS全然悪くないじゃないか。はい、疑ってすいませんでした。さてGoogleさん、2021/3/25時点でまだ修正されておりませんので、はよ日本と判定されるように対応いただけると助かりますm(_ _)m

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

boto3でCloudSearch検索ドメインに複合クエリ問合せをする

↑をboto3でやってみました。

cloudsearchdomain.py
import boto3

endpoint_url = 'https://search-todofuken-XXXXXXXXXXXXXXXXXXXXXXXXX.ap-northeast-1.cloudsearch.amazonaws.com'

cloudsearchdomain = boto3.client(
    'cloudsearchdomain', endpoint_url=endpoint_url)

query = """
(and 
  prefecture:'{prefecture}'
  city:'{city}'
  (range
    field=zip [{from_zip}, {to_zip}]
  )
)
""".format(prefecture='宮城県', city='仙台市', from_zip=9830000, to_zip=9830100)

returnFields = [
    'city',
    'kana_town',
]

results = cloudsearchdomain.search(
                query=query,
                queryParser='structured',
                returnFields=','.join(returnFields),
                size=3)

print('found:%d' % results["hits"]["found"])
print('count:%d' % len(results["hits"]["hit"]))

for item in results["hits"]["hit"]:
    print(item["fields"])
$ python cloudsearchdomain.py
found:34
count:3
{'city': ['仙台市宮城野区'], 'kana_town': ['タゴ']}
{'city': ['仙台市宮城野区'], 'kana_town': ['タゴニシ']}
{'city': ['仙台市宮城野区'], 'kana_town': ['ツルマキ']}

CloudSearchのboto3のサンプルコードあんまり転がってないので参考になれば。

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

boto3でCloudSearch検索ドメインに複合クエリを実行する

↑をboto3でやってみました。

cloudsearchdomain.py
import boto3

endpoint_url = 'https://search-todofuken-XXXXXXXXXXXXXXXXXXXXXXXXX.ap-northeast-1.cloudsearch.amazonaws.com'

cloudsearchdomain = boto3.client(
    'cloudsearchdomain', endpoint_url=endpoint_url)

query = """
(and 
  prefecture:'{prefecture}'
  city:'{city}'
  (range
    field=zip [{from_zip}, {to_zip}]
  )
)
""".format(prefecture='宮城県', city='仙台市', from_zip=9830000, to_zip=9830100)

returnFields = [
    'city',
    'kana_town',
]

results = cloudsearchdomain.search(
                query=query,
                queryParser='structured',
                returnFields=','.join(returnFields),
                size=3)

print('found:%d' % results["hits"]["found"])
print('count:%d' % len(results["hits"]["hit"]))

for item in results["hits"]["hit"]:
    print(item["fields"])
$ python cloudsearchdomain.py
found:34
count:3
{'city': ['仙台市宮城野区'], 'kana_town': ['タゴ']}
{'city': ['仙台市宮城野区'], 'kana_town': ['タゴニシ']}
{'city': ['仙台市宮城野区'], 'kana_town': ['ツルマキ']}

CloudSearchのboto3のサンプルコードあんまり転がってないので参考になれば。

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

AWSでアプリをデプロイする方法(データベース作成編)

EC2の初期設定後本番環境でデータベースを作成する手順を紹介します(学習記録):fountain:

ここでは、MySQLから派生した「MariaDB」というデータベースを利用します。Amazon Linux 2を利用している場合、MariaDBは 「yumコマンド」からインストールできます。

# EC2内で実行
[ec2-user@<自身のElasiticIP> ~]$ sudo yum -y install mysql56-server mysql56-devel mysql56 mariadb-server mysql-devel

データベースの起動

・systemctlコマンド
systemctlコマンドは、Amazon LinuxやCentOSに含まれているもので、インストールしたソフトウェアの起動を一括して行えるツールです。

# データベースを起動
[ec2-user@<自身のElasiticIP> ~]$ sudo systemctl start mariadb

# 確認
[ec2-user@<自身のElasiticIP> ~]$ sudo systemctl status mariadb

# 「active (running) 」と緑色の表示がされれば、データベースの起動は成功です。

データベースのrootパスワードの設定

yumでインストールしたMariaDBには、デフォルトで「root」というユーザーでアクセスできるようになっていますが、パスワードは設定されていません。なので、パスワードを設定する必要があります。

[ec2-user@<自身のElasticIP> ~]$ sudo /usr/bin/mysql_secure_installation
# その後以下のステップを踏む
1.Enter current password for root (enter for none): 」と表示されたらEnterキーを押す
2.「Set root password? [Y/n]」と表示されたら「Y」を入力してEnterキーを押す
3.「New password:」と表示されたら自身で決めたパスワードを入力(※とくに画面には何も表示されませんが入力できています)
4.「Re-enter new password:」と表示されたら、同じパスワードを入力(とくに画面には何も表示されませんが入力できています)
5.「Remove anonymous users? [Y/n]」と表示されたら「Y」を入力してEnterキーを押す
6.「Disallow root login remotely? [Y/n]」と表示されたら「Y」を入力してEnterキーを押す
7.「Remove test database and access to it? [Y/n]」と表示されたら「Y」を入力してEnterキーを押す
8.「Reload privilege tables now? [Y/n]」と表示されたら「Y」を入力してEnterキーを押す

# データベースへの接続を確認
[ec2-user@<自身のElasticIP> ~]$ mysql -u root -p

# 「Enter password:」とパスワードを入力するように表示されるので、さきほど設定したパスワードを入力して、Enterキーを押してください
# 「exit」と入力すれば抜け出すことができます。

以上が本番環境でデータベースを作成する手順になります!参考までにどうぞ!

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

セッションマネージャでRDSに接続

案件で別契約のサーバのアプリケーションでDBを参照したいとの要望でためしてみました。
間違い等ありましたら、ご指摘ください!

TODO

別サーバからSSMでトンネリング

PrivateSubnetのEC2からRDSにポートフォワード

ssm_rds.png

前提

  • macOS v10.15.7
  • Terraform v0.13.4
  • RDSと接続元EC2(図①)は作成済

手順

1. ローカル

ここでは、PrivateSubnetにトンネリング用EC2(図②)を作成します。
SSHキーを作成して、/www/www.pubに公開鍵を貼り付けます。

※Terraformのコードは一部抜粋になりますので、ご自身の環境に合わせて適宜変更して下さい。

/modules/rds/output.tf
output "mysql_security_id" {
  value = aws_security_group.mysql.id
}
www/main.tf
### 省略 ###
# PortForward(EC2→RDS)
module "ssm" {
  source = "../modules/ssm"

  env               = var.env
  service_name      = var.service_name
  # subnet_id
  db_subnet         = var.db_subnet
  mysql_security_id = module.rds.mysql_security_id
}
modules/ssm/install.sh
#!/bin/bash

# SessionManager
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent

# MySQL Client
sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
sudo yum install -y mysql-community-client.x86_64
modules/ssm/main.tf
variable "env" {}
variable "service_name" {}
variable "db_subnet" {}
variable "mysql_security_id" {}
locals {
  name = "${var.env}-${var.service_name}-rds"
}

# ロール
data "aws_iam_instance_profile" "systems_manager" {
  name = "MyInstanceSSMProfile"
}
############
# EC2(SessionManager用)
############
resource "aws_instance" "private" {
  ami                    = "ami-0f310fced6141e627"
  instance_type          = "t2.micro"
  iam_instance_profile   = data.aws_iam_instance_profile.systems_manager.name
  subnet_id              = var.db_subnet
  vpc_security_group_ids = [var.mysql_security_id]
  key_name               = aws_key_pair.secret.id

  tags = {
    Name = local.name
  }
  user_data = file("../modules/ssm/install.sh")
}
resource "aws_key_pair" "secret" {
  key_name   = local.name
  public_key = file("./${var.env}.pub")
}

2. 接続元

接続元EC2(図①)で作業を行います。
まず、セッションマネージャ/ MySQLコマンドのインストールをします。
そして、shellscriptでセッションを開いてください。

$ sudo su
[root]$ aws configure
AWS Access Key ID [****************XXXX]: 
AWS Secret Access Key [****************XXXX]: 
Default region name [ap-northeast-1]: 
Default output format [json]: 
[root]$ cd
[root]$ vi .ssh/rds-tun.pem
#### ローカルで作成した秘密鍵を貼り付ける ####
xxxxxxxxxxxxxxxxx

[root]$ chmod 700 .ssh/ && chmod 600 .ssh/rds-tun.pem
[root]$ vi install.sh
#!/bin/bash

# SessionManager
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent

# MySQL Client
sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
sudo yum install -y mysql-community-client.x86_64

[root]$ chmod +x install.sh && ./install.sh

セッションマネージャ起動

nohup &が重要です。
通常start-sessionを受け付けると、他の入力ができなくなってしまうのでバックグラウンドで実行させる必要がありました。
今回は、cronで監視して動かす方法を取るので処理を止めないようにしています。
(後続のrdsに対するポートフォワードを行わなくてはアプリが動かないので...)

[root]$ vi session_open.sh
#!/bin/bash

lsof -i:12345 -P | grep "LISTEN"
if [ $? = 1 ]; then
    nohup aws ssm start-session --target i-xxxxxxxxxxx --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["22"],"localPortNumber":["12345"]}' &
fi

sleep 5

lsof -i:23456 -P | grep "LISTEN"
if [ $? = 1 ]; then
    ssh -f -N -L 23456:sample.cluster-xxxxxx.ap-northeast-1.rds.amazonaws.com:3306 -i ~/.ssh/rds-tun.pem ec2-user@127.0.0.1 -p 12345
fi

[root]$ chmod +x session_open.sh
[root]$ ./session_open.sh

3. MySQL接続

コマンド実行してみます。

[root]$ mysql -u sampleuser -h 127.0.0.1 -p -P 23456
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 000000
Server version: 5.7.12 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

sampleuser@127.0.0.1 [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| sample_db          |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.02 sec)

アプリケーションでの接続例は下記です。
DB_HOSTとDB_PORTを間違えるとつながらないので注意しましょう!
言わずもがなですが、DB_PORTは図②からRDSに向けてのポートです。

Laravel/.env
DB_CONNECTION=mysql
+ DB_HOST=127.0.0.1
+ DB_PORT=23456
DB_DATABASE=sample_db
DB_USERNAME=sampleuser
DB_PASSWORD=samplepassword

4. cron

セッションは定期的に切れてしまうので、30分に一回Portを確認させます。

[root]$ cd
[root]$ wget https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv
[root]$ crontab -e
# セッションマネージャ死活監視
*/30 8-18 * * 1-5 grep `date "+\%Y/\%-m/\%-d"`, syukujitsu.csv > /dev/null || ./session_open.sh;

おまけ

CUIでセッションを終わらせたい人のおまけです。
GUIでセッションを終わらせる方はこちら

session_stop.sh
#!/bin/bash

sid=`aws ssm describe-sessions --state Active | grep "username" | head -n 1 | awk '{print $2}' | sed -e 's/\"//g' -e 's/\,//'`
aws ssm terminate-session --session-id $sid

感想

nohupを知らなかったので、セッションが止まってしまう〜と悩んでいました...
勉強不足を感じたのでLinuxのコマンドをさらっておこうと思いました(粉みかん) :tea:

参考

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html
https://dev.classmethod.jp/articles/ssh-through-session-manager/
https://dev.classmethod.jp/articles/try-port-forwarding-with-ssm/
https://qiita.com/syoimin/items/6f5f0aca002161d40233
https://www.karakaram.com/aws-session-manager-tunneling-support-for-ssh/
https://nextat.co.jp/staff/archives/215

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

AWSとAzureのサイト間VPNをスクリプトで有効化・無効化する

はじめに

AWSとAzureのサイト間VPNを開発で使用しています。
VMは必要なときだけ起動するようにしていますが、VPNについては常に有効な状態にしておくという使い方をしていました。

開発が落ち着きVMを停止している時間が多くVPNの課金が目立つようになり、AWSで約3,700円、Azureで約3,000円の合計約6,700円が毎月発生していますので、微々たるものですが使わないときは止めておこうと思い立ち、スクリプトでの制御を行うようにしてみました。

オフィシャルで用意されているAPI(SDK)を叩いているだけなので、特に難しいことをやっている訳ではありませんが、一例としてご紹介します。

前提

  • AWSとAzureのサイト間VPNを使用している。
  • AWSおよびAzureのAPI(SDK)を実行できる状態になっている。
  • 各クラウドのVPNやネットワークの設定、SDKを使用してのAPIの実行方法は本記事では省略致します。
  • 本記事では、Azureのリソースやオブジェクトをサービスと表現しています。
  • 本記事で紹介する方法やスクリプトは動作を保障するものではありません。必ず環境のバックアップや動作後の確認を行ってください。

制御するサービス

以下はAWSとAzure間のサイト間VPN例です。
Untitled Diagram-Page-1 (2).png

課金が発生するサービスはAWS、Azureともにリモートゲートウェイの部分になります。
※ ①VPNGatewayと④VirtualNetworkGateway の部分
これらのサービスさえ停止すれば課金が発生しなくなりますが、中途半端にローカルゲートウェイが有効になった状態になっていると、設定しているIPアドレス宛にコネクションを張ってしまう可能性があるかと思いますので、VPNに関するサービス(ローカルゲートウェイなど)も停止します。

※ パブリックゲートウェイを停止するとIPアドレスも開放されるため、そのIPアドレスは他のユーザーが使用している可能性があるため接続先としての設定したままにするのは宜しくない。
※ 各クラウドで不要な通信は発生しないような設計になっていることも考えられますが、無効なIPアドレスなので止めておきましょう。

サービスの制御方法

VPNゲートウェイの設定は残したまま、停止・再開を行いたいところなのですが、
その様なステータスや設定が見当たらず、停止するにはサービス自体を削除するしか無いようです。
(もし停止できたとしても課金はされそうな気がします)
ですので、タイトルでは有効化・無効化としておりますが、実際には削除・再作成することで制御を行います。

有効化の手順(実行するAPI)

Untitled Diagram-Page-2 (1).png

1. Azure VirtualNetworkGateways(create)

Azureの仮想ネットワークゲートウェイを起動します。
※ このAPIの実行は非常に時間がかるため運用には一工夫が要ります。(後術します)

2. Azure PublicIPAddresses(get)

「1. Azure VirtualNetworkGateways」で起動した、Azure側のVPNゲートウェイのパブリックIPアドレスを取得します。

3. AWS CreateCustomerGateway

AWSのカスタマゲートウェイを作成します。
作成する際に、「2. Azure PublicIPAddresses」で取得したAzure側のパプリックIPを設定します。

4. AWS CreateVpnConnection

AWSのVPNコネクションを作成します。
作成した際のレスポンスに、VPNの設定情報がXML形式で含まれているので、
XMLをパースして事前共有キーとAWS側のパブリックIPアドレスを取得します。

5. AWS CreateVpnConnectionRoute

AWSのVPNコネクションにルート情報を作成します。

6. AWS CreateTags

「4. AWS CreateVpnConnection」と「5. AWS CreateVpnConnectionRoute」で作成したサービスに任意のダグを付けます。

7. Azure LocalNetworkGateway(create)

Azureのローカルネットワークゲートウェイを作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWS側のパブリックIPアドレスを設定します。

8. Azure VirtualNetworkGatewayConnection(create)

Azureの仮想ネットワークゲートウェイ接続を作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWSのVPNゲートウェイで生成した事前共有キーを設定します。

以上で有効化が完了です!

無効化の手順(実行するAPI)

Untitled Diagram-Page-3.png

1. AWS DeleteVpnConnection

有効化で付与したタグを元にAWSのVPNConnectionを削除します。

2. AWS DeleteCustomerGateway

有効化で付与したタグを元にAWSのカスタマーゲートウェイを削除します。

3. Azure VirtualNetworkGatewayConnections(delete)

Azureの仮想ネットワークゲートウェイ接続を削除します。

4. Azure VirtualNetworkGateways(delete)

Azureの仮想ネットワークゲートウェイを削除します。

5. Azure LocalNetworkGateways(delete)

Azureのローカルネットワークゲートウェイを削除する。

以上で無効化が完了です!

AzureのVirtualNetworkGatewayの起動に時間がかかる問題

上記のスクリプトで一部問題があります。
Azureの仮想ネットワークゲートウェイの作成が完了するまで、非常に時間がかかる場合があるのです。
調子が良いときは30分ほど、時間がかかるときは1時間ほど待たされます。
急用で使いたいときに1時間以上待たされてしまうのは困るので、営業日の業務時間前に、
仮想ネットワークゲートウェイのみ作成しておき必要に応じてその他を作成する、という運用もよいかと思います。
他のAPIは数分で完了するため、VMの起動などを待っている間にVPNを有効にすることができます。

参考スクリプト(Python3での例)

main.py
import sys
import boto3
import logging
from azure.mgmt.resource import SubscriptionClient
from azure.mgmt.network import NetworkManagementClient
from azure.common.client_factory import get_client_from_auth_file
from xml.etree import ElementTree

logging.basicConfig(level=logging.INFO, format="%(asctime)s :%(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# aws
aws_setting = {
    # ec2Tag is Favorite tag name eg) VPN_AZURE
    'ec2Tag': '<VPN_AZURE>',
    'accessKey': '<ACCESS_KEY>',
    'secretAccessKey': '<SECRET_ACCESS_KEY>',
    'DestinationCidrBlock': '<DESTINATION_CIDR_BLOCK>',
    'VpnGatewayId': '<VPN_GATEWAY_ID>'
}

# azure
azure_setting = {
    "subscriptionId": '<SUBSCRIPTION_ID>',
    "resourceGroupName": '<RESOURCE_GROUP_NAME>',
    "virtualNetworkGatewayConnectionName": '<VIRTUAL_NETWORK_GATEWAY_CONNECTION_NAME>',
    "virtualNetworkGatewayName": '<VIRTUAL_NETWORK_GATEWAY_NAME>',
    "localNetworkGatewayName": '<LOCAL_NETWORK_GATEWAY_NAME>',
    "virtualNetworkGatewayPublicIpName": '<VIRTUAL_NETWORK_GATEWAY_PUBLIC_IP_NAME>',
    # eg) ['10.0.0.0/16']
    "addressPrefixes": ['<ADDRESS_PREFIXES>'],
    # eg) /subscriptions/******/resourceGroups/hogehoge/providers/Microsoft.Network/virtualNetworkGateways/*******
    "virtualNetworkGateway1Id": '<VIRTUAL_NETWORK_GATEWAY1_ID>',
    "localNetworkGateway2Id": '<LOCAL_NETWORK_GATEWAY2_ID>',
    "subnetId": "<SUBNET_ID>",
    "publicIPAddressId": "<PUBLIC_IP_ADDRESS_ID>",
    # eg) Japan East
    "location": '<LOCATION>',
}

def usage_and_exit():
    print("Usage: python3 main.py [enable|disable]")
    exit()

def create_network_clinet():
    return get_client_from_auth_file(NetworkManagementClient, auth_path="../credential.json")

def create_ec2():
    return boto3.client('ec2',
        aws_access_key_id= aws_setting.get('accessKey'),
        aws_secret_access_key= aws_setting.get('secretAccessKey'),
        region_name='ap-northeast-1')

def enable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("Azure Create VPNGateway")
    poller = network_client.virtual_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
        {
            "sku": {
                "name": "Basic",
                "tier": "Basic"
            },
            "gatewayType": "Vpn",
            "vpnType": "RouteBased",
            "ipConfigurations": [
                {
                    "name": azure_setting.get("virtualNetworkGatewayName"),
                    "privateIPAllocationMethod": "Dynamic",
                    "subnet": {
                        "id": azure_setting.get("subnetId"),
                    },
                    "publicIPAddress": {
                        "id": azure_setting.get("publicIPAddressId"),
                    }
                }
            ],
            "location": azure_setting.get("location"),
        })
    azure_vpn_gateway = poller.result()
    logger.info("Azure Create VPNGateway ... complete!")

    logger.info("Azure Get PublicIP")
    publicIpAddresses = network_client.public_ip_addresses.get(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayPublicIpName")
    )
    logger.info("Azure Get PublicIP ... complete!")

    azure_ip = publicIpAddresses.ip_address
    logger.info("Get Azure IP: {0}".format(azure_ip))

    logger.info("AWS Create VPNGateway")
    customer_gateway = ec2.create_customer_gateway(
            PublicIp= azure_ip,
            Type= "ipsec.1",
            BgpAsn= 65000)
    logger.info("AWS Create CustomerGateway ... complete!")

    vpn_gateway_id = customer_gateway.get('CustomerGateway').get('CustomerGatewayId')
    logger.info("AWS Get CustomerGatewayId : {0}".format(vpn_gateway_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection = ec2.create_vpn_connection(
            CustomerGatewayId= vpn_gateway_id,
            VpnGatewayId= aws_setting.get('VpnGatewayId'),
            Type= "ipsec.1",
            Options={
                'StaticRoutesOnly': True
            })
    logger.info("AWS Create VPNConnection ... complete!")

    vpn_connection_id = vpn_connection.get('VpnConnection').get('VpnConnectionId')
    logger.info("AWS Get VpnConnectionId : {0}".format(vpn_connection_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection_route = ec2.create_vpn_connection_route(
        DestinationCidrBlock= aws_setting.get('DestinationCidrBlock'),
        VpnConnectionId= vpn_connection_id
    )
    logger.info("AWS Create VPNConnection ... complete!")

    xml = vpn_connection.get('VpnConnection').get('CustomerGatewayConfiguration')
    elem = ElementTree.fromstring(xml)

    ip = elem.find('ipsec_tunnel').find('vpn_gateway').find('tunnel_outside_address').find('ip_address').text
    key = elem.find('ipsec_tunnel').find('ike').find('pre_shared_key').text
    # logger.info("AWS PublicIP: {0}\nAWS SharedKey: {1}".format(ip, key))

    logger.info("AWS Create Tags")
    create_tags = ec2.create_tags(
        Resources= [
            vpn_gateway_id,
            vpn_connection_id
        ],
        Tags= [{
            'Key': 'Name',
            'Value': aws_setting.get('ec2Tag')
        }]
    )
    logger.info("AWS Create Tags ... complete!")

    logger.info("Azure Create LocalNetworkGateway")
    poller = network_client.local_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
        {
                "gatewayIpAddress": ip,
                "localNetworkAddressSpace": {
                    "addressPrefixes": azure_setting.get("addressPrefixes")
                },
                "location": azure_setting.get("location")
        }
    )
    result = poller.result()
    logger.info("Azure Create LocalNetworkGateway ... complete!")

    logger.info("Azure Create virtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
        {
            "sharedKey": key,
            "virtualNetworkGateway1": {
                "id": azure_setting.get("virtualNetworkGateway1Id")
            },
            "localNetworkGateway2": {
                "id": azure_setting.get("localNetworkGateway2Id"),
            },
            "connectionType2": "IPsec",
            "connectionProtocol": "IKEv2",
            "connectionMode": "Default",
            "location": azure_setting.get("location"),
            "connectionType": "IPsec"
        }
    )
    result = poller.result()
    logger.info("Azure Create virtualNetworkGatewayConnection ... complete!")

    logger.info("all completed !")

def disable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("AWS Get DescribeVpnConnection")
    vpn_connections = ec2.describe_vpn_connections(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete VPNConnection")
    if len(vpn_connections.get('VpnConnections')) >= 1:
        ec2.delete_vpn_connection(VpnConnectionId= vpn_connections.get('VpnConnections')[0].get('VpnConnectionId'))
        logger.info("AWS Delete VPNConnection ... complete!")
    else:
        logger.info("AWS Skip Delete VPNConnection")

    logger.info("AWS Get DescribeVpnConnection")
    customer_gateways = ec2.describe_customer_gateways(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete CustomerGateways")
    if len(customer_gateways.get('CustomerGateways')) >= 1:
        ec2.delete_customer_gateway(CustomerGatewayId= customer_gateways.get('CustomerGateways')[0].get('CustomerGatewayId'))
        logger.info("AWS Delete CustomerGateways ... complete!")
    else:
        logger.info("AWS Skip Delete CustomerGateways")

    logger.info("Azure Delete VirtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
    )
    ret = poller.result()
    logger.info("Azure Delete VirtualNetworkGatewayConnection ... complete!")

    logger.info("Azure Delete VirtualNetworkGateway")
    poller = network_client.virtual_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete VirtualNetworkGateway ... complete!")

    logger.info("Azure Delete LocalNetworkGateway")
    poller = network_client.local_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete LocalNetworkGateway ... complete!")
    logger.info("all completed !")

args = sys.argv
if len(args) < 2:
    usage_and_exit()

if args[1] == 'enable':
    enable()
elif args[1] == 'disable':
    disable()
else:
    usage_and_exit()

おまけ

上記のサンプルスクリプトをGitHubにアップしています。
https://github.com/Piecemeal-Technology-Inc/aws-azure-vpn-activation
※サンプルスクリプトは動作を保証するものではありません。コマンド実行する際にはVPN環境のバックアップや、有効後・無効後のクラウドの状態確認を必ず実施してください。

スクリプトの使い方

使用しているクラウドの認証情報を設定して頂き、以下のコマンドでVPNを有効・無効化できます。

VPNを有効にする
python3 ./main.py enable
VPNを無効にする
python3 ./main.py disable
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSとAzureのサイト間VPNをスクリプトで有効化・無効化する

はじめに

AWSとAzureのサイト間VPNを開発で使用しています。
VMは必要なときだけ起動するようにしていますが、VPNについては常に有効な状態にしておくという使い方をしていました。

開発が落ち着きVMを停止している時間が多くVPNの課金が目立つようになり、AWSで約3,700円、Azureで約3,000円の合計約6,700円が毎月発生していますので、微々たるものですが使わないときは止めておこうと思い立ち、スクリプトでの制御を行うようにしてみました。

オフィシャルで用意されているAPI(SDK)を叩いているだけなので、特に難しいことをやっている訳ではありませんが、一例としてご紹介します。

前提

  • AWSとAzureのサイト間VPNを使用している。
  • AWSおよびAzureのAPI(SDK)を実行できる状態になっている。
  • 各クラウドのVPNやネットワークの設定、SDKを使用してのAPIの実行方法は本記事では省略致します。
  • 本記事の内容は、動作を保障するものではありません。同様の方法で制御を行う場合は、環境のバックアップや動作の確認を必ず実施してください。
  • 本記事では、Azureのリソースやオブジェクトをサービスと表現しています。

制御するサービス

以下はAWSとAzure間のサイト間VPN例です。
Untitled Diagram-Page-1 (2).png

課金が発生するサービスはAWS、Azureともにリモートゲートウェイの部分になります。
※ ①VPNGatewayと④VirtualNetworkGateway の部分
これらのサービスさえ停止すれば課金が発生しなくなりますが、中途半端にローカルゲートウェイが有効になった状態になっていると、設定しているIPアドレス宛にコネクションを張ってしまう可能性があるかと思いますので、VPNに関する他のサービス(ローカルゲートウェイなど)も停止します。

※ リモートゲートウェイを停止するとパブリックIPアドレスも開放され、そのIPアドレスを他のユーザーが使用する可能性があるため接続先としての設定したままにするのは宜しくない。
※ 各クラウドで不要な通信は発生しないような設計になっていることも考えられますが、無効なIPアドレスなので止めておきましょう。

制御方法

当該のサービスを一時的に停止・再開することができればよいのですが、APIのドキュメントなどを確認してみても、そのようなステータスが見つかりませんでした。
(もし一時停止てきたとしても、リソースが確保されている状態となり課金は継続しそうな予感がします)
ですので、タイトルでは有効化・無効化としていますが、実際にはサービスを削除・再作成する形で実現しています。

有効化の手順(実行するAPI)

Untitled Diagram-Page-2 (1).png

1. Azure VirtualNetworkGateways(create)

Azureの仮想ネットワークゲートウェイを起動します。
※ このAPIの実行は非常に時間がかるため運用には一工夫が要ります。(後術します)

2. Azure PublicIPAddresses(get)

「1. Azure VirtualNetworkGateways」で起動した、Azure側のVPNゲートウェイのパブリックIPアドレスを取得します。

3. AWS CreateCustomerGateway

AWSのカスタマゲートウェイを作成します。
作成する際に、「2. Azure PublicIPAddresses」で取得したAzure側のパプリックIPを設定します。

4. AWS CreateVpnConnection

AWSのVPNコネクションを作成します。
作成した際のレスポンスに、VPNの設定情報がXML形式で含まれているので、
XMLをパースして事前共有キーとAWS側のパブリックIPアドレスを取得します。

5. AWS CreateVpnConnectionRoute

AWSのVPNコネクションにルート情報を作成します。

6. AWS CreateTags

「4. AWS CreateVpnConnection」と「5. AWS CreateVpnConnectionRoute」で作成したサービスに任意のダグを付けます。

7. Azure LocalNetworkGateway(create)

Azureのローカルネットワークゲートウェイを作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWS側のパブリックIPアドレスを設定します。

8. Azure VirtualNetworkGatewayConnection(create)

Azureの仮想ネットワークゲートウェイ接続を作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWSのVPNゲートウェイで生成した事前共有キーを設定します。

以上で有効化が完了です!

無効化の手順(実行するAPI)

Untitled Diagram-Page-3.png

1. AWS DeleteVpnConnection

有効化で付与したタグを元にAWSのVPNConnectionを削除します。

2. AWS DeleteCustomerGateway

有効化で付与したタグを元にAWSのカスタマーゲートウェイを削除します。

3. Azure VirtualNetworkGatewayConnections(delete)

Azureの仮想ネットワークゲートウェイ接続を削除します。

4. Azure VirtualNetworkGateways(delete)

Azureの仮想ネットワークゲートウェイを削除します。

5. Azure LocalNetworkGateways(delete)

Azureのローカルネットワークゲートウェイを削除する。

以上で無効化が完了です!

AzureのVirtualNetworkGatewayの起動に時間がかかる問題

上記のスクリプトで一部問題があります。
Azureの仮想ネットワークゲートウェイの作成が完了するまで、非常に時間がかかる場合があるのです。
調子が良いときは30分ほど、時間がかかるときは1時間ほど待たされます。
急用で使いたいときに1時間以上待たされてしまうのは困るので、営業日の業務時間前に、
仮想ネットワークゲートウェイのみ作成しておき必要に応じてその他を作成する、という運用もよいかと思います。
他のAPIは数分で完了するため、VMの起動などを待っている間にVPNを有効にすることができます。

参考スクリプト(Python3での例)

main.py
import sys
import boto3
import logging
from azure.mgmt.resource import SubscriptionClient
from azure.mgmt.network import NetworkManagementClient
from azure.common.client_factory import get_client_from_auth_file
from xml.etree import ElementTree

logging.basicConfig(level=logging.INFO, format="%(asctime)s :%(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# aws
aws_setting = {
    # ec2Tag is Favorite tag name eg) VPN_AZURE
    'ec2Tag': '<VPN_AZURE>',
    'accessKey': '<ACCESS_KEY>',
    'secretAccessKey': '<SECRET_ACCESS_KEY>',
    'DestinationCidrBlock': '<DESTINATION_CIDR_BLOCK>',
    'VpnGatewayId': '<VPN_GATEWAY_ID>'
}

# azure
azure_setting = {
    "subscriptionId": '<SUBSCRIPTION_ID>',
    "resourceGroupName": '<RESOURCE_GROUP_NAME>',
    "virtualNetworkGatewayConnectionName": '<VIRTUAL_NETWORK_GATEWAY_CONNECTION_NAME>',
    "virtualNetworkGatewayName": '<VIRTUAL_NETWORK_GATEWAY_NAME>',
    "localNetworkGatewayName": '<LOCAL_NETWORK_GATEWAY_NAME>',
    "virtualNetworkGatewayPublicIpName": '<VIRTUAL_NETWORK_GATEWAY_PUBLIC_IP_NAME>',
    # eg) ['10.0.0.0/16']
    "addressPrefixes": ['<ADDRESS_PREFIXES>'],
    # eg) /subscriptions/******/resourceGroups/hogehoge/providers/Microsoft.Network/virtualNetworkGateways/*******
    "virtualNetworkGateway1Id": '<VIRTUAL_NETWORK_GATEWAY1_ID>',
    "localNetworkGateway2Id": '<LOCAL_NETWORK_GATEWAY2_ID>',
    "subnetId": "<SUBNET_ID>",
    "publicIPAddressId": "<PUBLIC_IP_ADDRESS_ID>",
    # eg) Japan East
    "location": '<LOCATION>',
}

def usage_and_exit():
    print("Usage: python3 main.py [enable|disable]")
    exit()

def create_network_clinet():
    return get_client_from_auth_file(NetworkManagementClient, auth_path="../credential.json")

def create_ec2():
    return boto3.client('ec2',
        aws_access_key_id= aws_setting.get('accessKey'),
        aws_secret_access_key= aws_setting.get('secretAccessKey'),
        region_name='ap-northeast-1')

def enable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("Azure Create VPNGateway")
    poller = network_client.virtual_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
        {
            "sku": {
                "name": "Basic",
                "tier": "Basic"
            },
            "gatewayType": "Vpn",
            "vpnType": "RouteBased",
            "ipConfigurations": [
                {
                    "name": azure_setting.get("virtualNetworkGatewayName"),
                    "privateIPAllocationMethod": "Dynamic",
                    "subnet": {
                        "id": azure_setting.get("subnetId"),
                    },
                    "publicIPAddress": {
                        "id": azure_setting.get("publicIPAddressId"),
                    }
                }
            ],
            "location": azure_setting.get("location"),
        })
    azure_vpn_gateway = poller.result()
    logger.info("Azure Create VPNGateway ... complete!")

    logger.info("Azure Get PublicIP")
    publicIpAddresses = network_client.public_ip_addresses.get(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayPublicIpName")
    )
    logger.info("Azure Get PublicIP ... complete!")

    azure_ip = publicIpAddresses.ip_address
    logger.info("Get Azure IP: {0}".format(azure_ip))

    logger.info("AWS Create VPNGateway")
    customer_gateway = ec2.create_customer_gateway(
            PublicIp= azure_ip,
            Type= "ipsec.1",
            BgpAsn= 65000)
    logger.info("AWS Create CustomerGateway ... complete!")

    vpn_gateway_id = customer_gateway.get('CustomerGateway').get('CustomerGatewayId')
    logger.info("AWS Get CustomerGatewayId : {0}".format(vpn_gateway_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection = ec2.create_vpn_connection(
            CustomerGatewayId= vpn_gateway_id,
            VpnGatewayId= aws_setting.get('VpnGatewayId'),
            Type= "ipsec.1",
            Options={
                'StaticRoutesOnly': True
            })
    logger.info("AWS Create VPNConnection ... complete!")

    vpn_connection_id = vpn_connection.get('VpnConnection').get('VpnConnectionId')
    logger.info("AWS Get VpnConnectionId : {0}".format(vpn_connection_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection_route = ec2.create_vpn_connection_route(
        DestinationCidrBlock= aws_setting.get('DestinationCidrBlock'),
        VpnConnectionId= vpn_connection_id
    )
    logger.info("AWS Create VPNConnection ... complete!")

    xml = vpn_connection.get('VpnConnection').get('CustomerGatewayConfiguration')
    elem = ElementTree.fromstring(xml)

    ip = elem.find('ipsec_tunnel').find('vpn_gateway').find('tunnel_outside_address').find('ip_address').text
    key = elem.find('ipsec_tunnel').find('ike').find('pre_shared_key').text
    # logger.info("AWS PublicIP: {0}\nAWS SharedKey: {1}".format(ip, key))

    logger.info("AWS Create Tags")
    create_tags = ec2.create_tags(
        Resources= [
            vpn_gateway_id,
            vpn_connection_id
        ],
        Tags= [{
            'Key': 'Name',
            'Value': aws_setting.get('ec2Tag')
        }]
    )
    logger.info("AWS Create Tags ... complete!")

    logger.info("Azure Create LocalNetworkGateway")
    poller = network_client.local_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
        {
                "gatewayIpAddress": ip,
                "localNetworkAddressSpace": {
                    "addressPrefixes": azure_setting.get("addressPrefixes")
                },
                "location": azure_setting.get("location")
        }
    )
    result = poller.result()
    logger.info("Azure Create LocalNetworkGateway ... complete!")

    logger.info("Azure Create virtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
        {
            "sharedKey": key,
            "virtualNetworkGateway1": {
                "id": azure_setting.get("virtualNetworkGateway1Id")
            },
            "localNetworkGateway2": {
                "id": azure_setting.get("localNetworkGateway2Id"),
            },
            "connectionType2": "IPsec",
            "connectionProtocol": "IKEv2",
            "connectionMode": "Default",
            "location": azure_setting.get("location"),
            "connectionType": "IPsec"
        }
    )
    result = poller.result()
    logger.info("Azure Create virtualNetworkGatewayConnection ... complete!")

    logger.info("all completed !")

def disable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("AWS Get DescribeVpnConnection")
    vpn_connections = ec2.describe_vpn_connections(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete VPNConnection")
    if len(vpn_connections.get('VpnConnections')) >= 1:
        ec2.delete_vpn_connection(VpnConnectionId= vpn_connections.get('VpnConnections')[0].get('VpnConnectionId'))
        logger.info("AWS Delete VPNConnection ... complete!")
    else:
        logger.info("AWS Skip Delete VPNConnection")

    logger.info("AWS Get DescribeVpnConnection")
    customer_gateways = ec2.describe_customer_gateways(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete CustomerGateways")
    if len(customer_gateways.get('CustomerGateways')) >= 1:
        ec2.delete_customer_gateway(CustomerGatewayId= customer_gateways.get('CustomerGateways')[0].get('CustomerGatewayId'))
        logger.info("AWS Delete CustomerGateways ... complete!")
    else:
        logger.info("AWS Skip Delete CustomerGateways")

    logger.info("Azure Delete VirtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
    )
    ret = poller.result()
    logger.info("Azure Delete VirtualNetworkGatewayConnection ... complete!")

    logger.info("Azure Delete VirtualNetworkGateway")
    poller = network_client.virtual_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete VirtualNetworkGateway ... complete!")

    logger.info("Azure Delete LocalNetworkGateway")
    poller = network_client.local_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete LocalNetworkGateway ... complete!")
    logger.info("all completed !")

args = sys.argv
if len(args) < 2:
    usage_and_exit()

if args[1] == 'enable':
    enable()
elif args[1] == 'disable':
    disable()
else:
    usage_and_exit()

おまけ

上記のサンプルスクリプトをGitHubで公開しています。
https://github.com/Piecemeal-Technology-Inc/aws-azure-vpn-activation

※サンプルスクリプトは動作を保障するものではありません。実行する前にVPN環境のバックアップや実行後のクラウドの状況確認を必ず実施してください。

使い方

AWSやAzureの認証情報などを設定して頂き、以下のコマンドで有効化、無効化を実行できます。

有効化
python3 ./main.py enable
無効化
python3 ./main.py disable
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

appsettings.jsonをReleaseやDebugで切り替える

AWSのElastic BeanstalkにASP.NET Core アプリケーションをデプロイする際、appsettings.jsonの切り替えが意外にも困ったのでまとめ。

appsettings.jsonとappsettings.Development.json、その他appsettings.xxxx.jsonなど「実行環境」の選択でappsettingsを切り替えることは簡単だが、ReleaseやDebugなど「ビルド構成」で切り替える機能は.NET Coreのアプリにはない。
いろんな方法があると思うが、csprojにmsbuildのコピーコマンドを手で追記することで、シンプルにビルド後にビルド後のディレクトリ内でappsettings.jsonをappsettings.xxx.jsonで上書きすることができた。

sample.csproj
  <Target Name="CopyStaging" AfterTargets="Build">
    <Copy SourceFiles="$(TargetDir)\settings\appsettings.$(Configuration).json" DestinationFiles="$(TargetDir)\appsettings.json" SkipUnchangedFiles="false" />
  </Target>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS DynamoDBに接続する

AWS DynamoDBに接続する

AWS DynamoDBを使用するWebサイトを開発するにあたって、
まずはサイトからDynamoDBに接続するための設定を行う必要がある。
いくつか方法があるようだが、マシンにAWSへの接続情報を持たせる方法についてメモ。

ローカルに開発環境を作成するための備忘録。

AWS認証情報の取得

まずは、AWSに接続するためのアクセスキー(認証情報)を作成し、保存する。

1.AWS マネジメントコンソール にサインインし、IAM コンソールを開く。
2.ユーザー→認証情報→アクセスキーの作成をクリック
3.アクセスキーが生成され、表示されるのでコピペするかCSVダウンロードするなりして保存

認証情報をサーバーに設定する

取得したAWSアクセスキーを、サーバーの環境変数に設定する。

AWS_ACCESS_KEY_ID:<取得したAccess key ID>
AWS_SECRET_ACCESS_KEY:<取得したSecret access key>

以上で、Webサーバーへの認証情報の設定は完了!
DynamoDBにつながるようになったよ!

試してみた環境(ローカルの開発環境)

 Windows10
 PHP/Laravel/Vue.js

参考

公式ドキュメントのこのあたり
DynamoDB (ウェブサービス) のセットアップ
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SettingUp.DynamoWebService.html#SettingUp.DynamoWebService.GetCredentials
環境変数からの認証情報の使用
https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/guide_credentials.html

 

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

Node.jsでAWSのパラメータストアから値を取得する

はじめに

シークレット系の情報をAWSのパラメータストアに格納し,それをNode.jsで呼び出してみました。
この記事はその際の備忘録になります。
誤っている箇所や修正したほうがいい箇所などありましたらコメントいただけますと幸いです。

AWSのパラメータストアとは

AWS System Manager の機能の一つ。
パスワードなどのシークレット系の情報をセキュアに保存することができます。

公式ページ : https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-parameter-store.html

必要なモジュールのインストール

AWS SDK for JavaScript をインストールします。

$ npm install aws-sdk

AWS SDK for JavaScript とは,AWSのサービスをjavascriptで使用するためのAPIになります。
今回はこのAPIを使用して,パラメータストアから値を取得します。

AWSの認証情報の設定

Node.jsでAWSのサービスを利用するに当たって,認証情報を設定する必要があります。
認証情報の設定方法はいくつかありますが,今回は ~/.aws/credentials から読み取る方法を使用したいと思います。

今回 ~/.aws/credentials には,defaultプロファイルとは別に,privateアカウントのプロファイルも記述しておきます。

~/.aws/credentials
[default]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>

[private]
aws_access_key_id = <PRIVATE_ACCOUNT_ACCESS_KEY_ID>
aws_secret_access_key = <PRIVATE_ACCOUNT_SECRET_ACCESS_KEY>

また,~/.aws/config ファイルには,region を記述しておきます。

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

[profile private]
output = json
region = ap-northeast-1

AWSで新規パラメータを作成

公式ドキュメントを参考に,新規パラメータを作成します。
今回は,~/.aws/credentials~/.aws/config にprivateというプロファイル名で記述したAWSアカウントのパラメータストアに,sampleというパラメータ名で 'sample' という文字列を保存しました。

実行ファイル作成

上記で作成したsampleというパラメータを取得します。

実行ファイルの完成形

sample.js
// aws-sdk をロードする前に,環境変数をセットする

// ~/.aws/config の情報を参照できるようにする
process.env.AWS_SDK_LOAD_CONFIG = true;

// 使用するprofileを指定する
process.env.AWS_PROFILE = 'private';

// 環境変数セット完了

// aws-sdk モジュールを読み込む
const AWS = require("aws-sdk");

// 新規 AWS.SSM class を作成
const ssm = new AWS.SSM();

// 'sample' という名前のパラメータを取得
const params = {
  Name: 'sample',
  WithDecryption: true
};
ssm.getParameter(params, (err, data) => {
  if (err) {
    console.log(err);
  } else {
    console.log(data);
  }
});

下記で各処理について説明していきます。

環境変数の設定

sample.js(一部)
// aws-sdk をロードする前に,環境変数をセットする

// ~/.aws/config の情報を参照できるようにする
process.env.AWS_SDK_LOAD_CONFIG = true;

// 使用するprofileを指定する
process.env.AWS_PROFILE = 'private';

// 環境変数セット完了

// aws-sdk モジュールを読み込む
const AWS = require("aws-sdk");

aws-sdk モジュールを読み込む前に,環境変数をセットします。
そうすることで,セットした環境変数の設定を元に aws-sdk モジュールの読み込みが行われます。

セットする環境変数

  • AWS_SDK_LOAD_CONFIG : ~/.aws/config に記述された設定を参照できるようにします。(参考
  • AWS_PROFILE : 使用するprofileを指定します。(参考

新規 AWS.SSM クラスを作成

sample.js(一部)
// 新規 AWS.SSM class を作成
const ssm = new AWS.SSM();

AWS.SSM クラスは,Node.js内で AWS System Manager の機能を利用するためのクラスになります。(参考)

パラメータを取得

sample.js(一部)
// 'sample' という名前のパラメータを取得
const params = {
  Name: 'sample',
  WithDecryption: true // パラメータの値が暗号化されていた場合は復号
};
ssm.getParameter(params, (err, data) => {
  if (err) {
    console.log(err);
  } else {
    console.log(data);
  }
});

AWS.SSM クラスに用意されている getParameter メソッドを使用して,パラメータストアから値を取得します。(参考

paramsに設定できるオプション

  • Name : パラメータ名
  • WithDecryption : パラメータの値が暗号化されていた場合に復号するかどうかを指定(パラメータの値が文字列または文字列リストの場合は,このオプションは無視されます)

実行

$ node sample.js
{
  Parameter: {
    Name: 'sample',
    Type: 'String',
    Value: 'sample',
    Version: 1,
    LastModifiedDate: 2021-03-23T07:45:35.557Z,
    ARN: 'arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/sample',
    DataType: 'text'
  }
}

参考

AWS 公式

その他

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

HTTPSの読み込みが遅いのを改善した話

HTTPS化したらやたらと読み込みが遅い時がある...

HTTPSでサイトにはアクセスできているのに、5回に一回くらい読み込みがされない...なんでだろうか?

結論から言ってしまうと...

ロードバランサーの役割をしっかり理解していないのが原因でした。

ロードバランサーとは?

ロード(load、負荷)+バランサー(Balancer、平衡を保つためのもの)で、サーバーやネットワークに関連する用語であり、装置の名称です。
この仕組みにより、Webサイトへのアクセス集中やサーバー故障などの場合でも、アクセス中の利用者に安定したサービス提供を継続可能になります

image.png

まとめるとロードバランサーの役割は大きく2つです。

①パフォーマンスの向上
サーバの追加台数分、クライアントへの対応を処理可能

②可用性が向上
特定のサーバがダウンしても、別のサーバでサービス継続可能

今回の謎の経緯

HTTPS化の方法はたくさんあるのですが、今回は「AWS Certificate Manager(ACM)」でやりました。
それに伴って、ロードバランサーは「Application Load Balancing(ALB)」を使用しておりました。

最初は「通信環境の不調なのか」、「インスタンス等の設定に不具合があるのか」はたまた「取得したドメインに問題があるのか?」など、今思うと的外れな心配ばかりしていました。色々と記事を辿っていくと「JavaScriptや画像が重いと読み込みが遅くなる」という内容の記事をいくつも見つけ、「なんかこれっぽいな...」と勝手に決めつけ、一つ一つ手当たり次第に原因を潰していく...といった感じで何に原因があるのかも理解できていない状態でした。

そこから検索のワードを少しずつ変えて記事を調べていくと、この記事に辿り着きました。

すぐに自分の直面している状態と似ていると思い、ロードバランサーの設定を見直しました。

ロードバランサーの設定を見直す

image.png

この時に本来であれば「パブリックサブネット(外部からのアクセス可能な空間)」を2つ選択しないとロードバランサーの意味を成さないのですが、今回作成したVPCには「パブリックサブネットが1つ」と「プライベートサブネットが2つ」しかなく、「少なくとも2つのサブネットを指定する必要あります」というメッセージ通りに設定するとなると、必然的に片方を「プライベートサブネット」にしなければ先に進めない状態でした。

原因はこれです。

「サブネットにアタッチされたインターネットゲートウェイが存在しません」という注意勧告を出していてくれたのにも関わらず、無知な僕は先に進めることを良しとし、メッセージの意味を理解していませんでした。

ロードバランサーは最初にも説明した通り「負荷分担装置」です。
その役割は、ユーザーからのアクセスを平等に割り振ることです。
察しの良いみなさんなら勘づいているでしょうが、「プライベートサブネット」に割り振られてもアクセスできないため、結局はもう一方の「パブリックサブネット」へと戻されます。

「読み込みの遅い」というのは、つまりそういうことでした。
来た道を戻ってまた別の道に行っていたらそりゃ遅くなりますよね。

解決方法

解決方法は簡単です。
VPCに「パブリックサブネット」を新たに追加してあげるだけです。
追加したサブネットをロードバランサーの作成時に設定してあげれば、通信がどちらに割り振られてもサーバーへのアクセスが可能となり、読み込み速度が改善されました。

最後に

今回の「ロードバランサー」同様に、性能の有能さに甘えてしまって、なんとなくで使ってしまっているツールがたくさんあるので、これを機にしっかりとその子が何を得意としているのかを理解してあげることが大事なんだなと痛感しました。
最後までお付き合いいただきありがとうございます。

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

ポートフォリオのHTTPSの読み込みが遅いのを改善した話

HTTPS化したらやたらと読み込みが遅い時がある...

HTTPSでサイトにはアクセスできているのに、5回に一回くらい読み込みがされない...なんでだろうか?

結論から言ってしまうと...

ロードバランサーの役割をしっかり理解していないのが原因でした。

ロードバランサーとは?

ロード(load、負荷)+バランサー(Balancer、平衡を保つためのもの)で、サーバーやネットワークに関連する用語であり、装置の名称です。
この仕組みにより、Webサイトへのアクセス集中やサーバー故障などの場合でも、アクセス中の利用者に安定したサービス提供を継続可能になります

image.png

まとめるとロードバランサーの役割は大きく2つです。

①パフォーマンスの向上
サーバの追加台数分、クライアントへの対応を処理可能

②可用性が向上
特定のサーバがダウンしても、別のサーバでサービス継続可能

今回の謎の経緯

HTTPS化の方法はたくさんあるのですが、今回は「AWS Certificate Manager(ACM)」でやりました。
それに伴って、ロードバランサーは「Application Load Balancing(ALB)」を使用しておりました。

最初は「通信環境の不調なのか」、「インスタンス等の設定に不具合があるのか」はたまた「取得したドメインに問題があるのか?」など、今思うと的外れな心配ばかりしていました。色々と記事を辿っていくと「JavaScriptや画像が重いと読み込みが遅くなる」という内容の記事をいくつも見つけ、「なんかこれっぽいな...」と勝手に決めつけ、一つ一つ手当たり次第に原因を潰していく...といった感じで何に原因があるのかも理解できていない状態でした。

そこから検索のワードを少しずつ変えて記事を調べていくと、この記事に辿り着きました。

すぐに自分の直面している状態と似ていると思い、ロードバランサーの設定を見直しました。

ロードバランサーの設定を見直す

image.png

この時に本来であれば「パブリックサブネット(外部からのアクセス可能な空間)」を2つ選択しないとロードバランサーの意味を成さないのですが、今回作成したVPCには「パブリックサブネットが1つ」と「プライベートサブネットが2つ」しかなく、「少なくとも2つのサブネットを指定する必要あります」というメッセージ通りに設定するとなると、必然的に片方を「プライベートサブネット」にしなければ先に進めない状態でした。

原因はこれです。

「サブネットにアタッチされたインターネットゲートウェイが存在しません」という注意勧告を出していてくれたのにも関わらず、無知な僕は先に進めることを良しとし、メッセージの意味を理解していませんでした。

ロードバランサーは最初にも説明した通り「負荷分担装置」です。
その役割は、ユーザーからのアクセスを平等に割り振ることです。
察しの良いみなさんなら勘づいているでしょうが、「プライベートサブネット」に割り振られてもアクセスできないため、結局はもう一方の「パブリックサブネット」へと戻されます。

「読み込みの遅い」というのは、つまりそういうことでした。
来た道を戻ってまた別の道に行っていたらそりゃ遅くなりますよね。

解決方法

解決方法は簡単です。
VPCに「パブリックサブネット」を新たに追加してあげるだけです。
追加したサブネットをロードバランサーの作成時に設定してあげれば、通信がどちらに割り振られてもサーバーへのアクセスが可能となり、読み込み速度が改善されました。

最後に

今回の「ロードバランサー」同様に、性能の有能さに甘えてしまって、なんとなくで使ってしまっているツールがたくさんあるので、これを機にしっかりとその子が何を得意としているのかを理解してあげることが大事なんだなと痛感しました。
最後までお付き合いいただきありがとうございます。

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

Docker/Kubernetes構成のためのPython開発プロジェクトの雛形

はじめに

PythonスクリプトをPipenv環境で開発し、実行するためのコンテナをEKS上で起動するというケースを想定しています。

次のソフトウェアを用います。

  • vim(ほかのエディタでも構いません)
  • venv
  • Pipenv
  • Python3.8
  • Docker
  • Amazon ECR
  • Amazon EKS

次の事項は扱いません。

  • 各構成要素のPros/Cons的な話
  • Amazon ECR/EKSのセットアップ(eksctlなどでクラスタを作れるようになっている必要があります)

構成ファイル

.
├── Pipenv(Pythonの環境管理)
├── Dockerfile(Pythonスクリプトを動作させるコンテナを定義)
├── deployment.yaml(Kubernetes上でコンテナを動作させるデプロイメント定義)
├── src
│   ├── main.py(開発対象のPythonスクリプト)

Python開発

サンプルスクリプト(./src/main.py)を作成して動作確認します。

サンプルスクリプト

今回は次のサンプルスクリプトを使用します。
仕様:1秒毎に標準出力に現在時刻を表示する。

./src/main.py
#
# 一秒毎に時刻を表示するプログラム
#

import time
import datetime

while 1 == 1:
    print("{}".format(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')))
    time.sleep(1)

今回は解説しませんが、Amazon Container Insightsを使うとKubernetesのPodのLogs(標準出力を含む)をCloud Watchで取得できるため、ローカル/Docker/Kubernetesのログ回りを単一のコードで実現できて便利です。サイドカー+Fluentなど専用のコードを入れる場合に比べ柔軟性は低いですが。

Pipenv環境を作る

次のPipfileを用意します。
dev-packagesの中身はvim環境のためのものなので、無くても実行できます。
(ご参考:『最低限の手間で、開発にも使えるVim環境を構築する。』

./Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[scripts]
dev = "python ./src/main.py"

[dev-packages]
flake8 = "*"
jedi = "*"
python-vim = "*"
pylint = "*"

[packages]

[requires]
python_version = "3.8"

Venv環境のセットアップ

# venv環境のセットアップ
# Python3が入っていない場合は事前に入れておいてください。
python3 -m venv .venv

Venv環境を有効にする

# b系シェルの場合
source .venv/bin/activate

# fishシェルの場合
source .venv/bin/activate.fish

Pipenv/必要パッケージをインストール

# Pipenvをインストール
pip install pipenv

# Pipfileを元に必要パッケージをインストール
pipenv install

Pythonスクリプトを実行する

venv環境を有効にした状態で、次のようにPipenvを用いてPythonスクリプトを実行します。
現在日時が一秒毎に表示されれば成功です。

pipenv run dev

> 2021/03/25 02:24:20
> 2021/03/25 02:24:21
> 2021/03/25 02:24:22
> 2021/03/25 02:24:23

コンテナの作成と実行

上記で作成したPythonプログラムを実行するコンテナを作成します
教科書通りのMulti-stage Build構成です。

./Dockerfile
#
# ビルダーコンテナ
#

FROM python:3.8-buster as builder

WORKDIR /app

COPY requirements.lock /app
RUN pip3 install -r requirements.lock

#
# メインコンテナ
#

FROM python:3.8-slim-buster as main
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages

COPY src /app

WORKDIR /app

ENTRYPOINT ["python", "/app/main.py"]

マルチステートビルドは本記事のスクリプト程度ではメリットを感じにくいですが、ミドルウェアを(特にコンパイルして)利用するプロジェクトなどでは実稼働コンテナに不要ファイルを生成する必要が無く有用です。

コンテナをビルド

# パッケージ情報を作成
# pipenvのパッケージ情報を出力します
pipenv lock -r > requirements.lock

# ビルドしてイメージを作成
# (一括してビルドする場合)
docker build -t <コンテナ名>:latest .

# 前段と後段を別々にビルドしたい場合は次の2つ
docker build --target builder -t <コンテナ名>:builder .
docker build --target main -t <コンテナ名>:latest .

コンテナを実行

docker run -it --rm <コンテナ名>:latest

> 2021/03/25 02:24:20
> 2021/03/25 02:24:21
> 2021/03/25 02:24:22
> 2021/03/25 02:24:23

Pipenvで実行した場合と同様、日時が1秒毎に表示されれば成功です。

Amazon EKSへデプロイする

次の事項はあらかじめ済ませておいてください。
・ECRのセットアップ
・ECRリポジトリの作成
・EKSのセットアップ
・eksctlのセットアップ(クラスタ作成で利用する場合)

ECRへコンテナイメージをプッシュする

ECRに作成したリポジトリにコンテナイメージをプッシュします。
リポジトリのURLは確認しておいてください。

手順は他のクラウドやDocker HUBと同様ですが、
ECRでは認証情報を引き込む必要があります。

# イメージプッシュ用のタグを作成
docker tag <コンテナ名>:latest <リポジトリURL>:latest

# ECRの認証情報をローカルに引き込む
aws ecr get-login-password --region <リージョン名:ap-northeast-1> | docker login --username AWS --password-stdin <リポジトリURLルート部分>

# プッシュを実行
docker push <リポジトリURL>:latest

EKSへデプロイ

本記事ではeksctlを用いてテスト用のクラスタを起動します。
EKSは更新が速いので、オプション構成は都度見直した方が良いかもしれません。
手元では起動に約20分かかります。

eksctl create cluster \
        --vpc-cidr 10.0.0.0/16 \
        --vpc-nat-mode HighlyAvailable \
        --name <クラスタ名> \
        --version 1.18 \
        --region <リージョン名:ap-northeast-1> \
        --nodegroup-name <ノードグループ名> \
        --node-type t3.large \
        --nodes 3 \
        --nodes-min 1 \
        --nodes-max 4

つぎの内容でデプロイメント用のyamlファイルを作成します
ネームスペースは別途作成しても良いですが、今回は同居させました。

deployment.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: <ネームスペース名>
  labels:
    name: <ネームスペース名>
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: <ワークロード名>
  namespace: <ネームスペース名>
  labels:
    app: <ワークロード名>
spec:
  replicas: 1
  progressDeadlineSeconds: 300
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: <ワークロード名>
  template:
    metadata:
      labels:
        app: <ワークロード名>
      namespace: <ネームスペース名>
    spec:
      containers:
      - name: <コンテナ名>
        image: <リポジトリURL>
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            cpu: "250m"
            memory: "256Mi"
          limits:
            cpu: "250m"
            memory: "256Mi"
      restartPolicy: Always
      securityContext: {}
      terminationGracePeriodSeconds: 30

次のコマンドでデプロイを実施します

kubectl apply -f deployment.yaml

デプロイ結果の確認

様々な方法があると思いますがシンプルにpod名を調べてlogsで確認してみます。
ワークロードの起動完了までに数分かかります。起動完了前でもkubectl logsコマンドを実行できますが何も取得できません。

# pod名を確認
# (本クラスタには記事のワークロードのみデプロイしていると想定しています)
kubectl get pods -n <ネームスペース名>

# podの出力を表示
# Pythonが一秒毎に表示している日付文字列が表示されれば成功です
kubectl logs <pod名> -n <ネームスペース名>
> 2021/03/25 02:24:20
> 2021/03/25 02:24:21
> 2021/03/25 02:24:22
> 2021/03/25 02:24:23
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda関数を作成する開発者に必要な権限を考えてみた

はじめに

日頃Lambda関数の構築において、都度インフラ管理者に依頼して、IAMポリシーとIAMロールを作成してもらうようにしていますが、後から必要な権限が出てきたりして、開発がなかなか進まないこともありますよね。
Lambda関数にアタッチするIAMポリシーとIAMロールを自分で作って試せると嬉しいなと思い、Lambda関数を構築する開発者に必要なIAM周りのIAMポリシーについて考えてみました。

先にお伝えすると、下記のポリシーだと穴があるので、ご使用はお控えください。
別のアイデア大歓迎です!コメントください!

前提条件

AWSの環境は次の通りとします。

Assume Roleを使う

Assume Roleを使っている場合、ユーザーに権限をつける際は下記のようなフローになります。

① IAMポリシーの作成
必要な権限をつけたIAMポリシーを作成する。

② IAMロールの作成
①で作成したポリシーをAssume用のロールにアタッチする。

③ IAMポリシーの作成
②のIAMロールにAssumeできる権限をつけたIAMポリシーを作成する。

④ IAMグループの作成
③番のIAMポリシーをIAMグループにアタッチする。

⑤ IAMユーザーの追加
④番で作成したIAMグループにユーザーを追加する。

今回はIAMポリシーとIAMロールの作成・更新ができる権限が必要です。しかし上記のフローより①③IAMポリシー、②IAMロールに権限が自由に付けられるのは避けたいです。

既にLambda関数のフルアクセスがついている

私にもともとついていた権限はLambda関数のフルアクセスで、IAM周りに関しては、こちらの8つがついているので、既存のIAMロールをLambda関数に付与することはできます。

  • iam:GetPolicy
  • iam:GetPolicyVersion
  • iam:GetRole
  • iam:GetRolePolicy
  • iam:ListAttachedRolePolicies
  • iam:ListRolePolicies
  • iam:ListRoles
  • iam:PassRole

ただ、自分でIAMポリシーを作成し、そのポリシーをアタッチしたIAMロールを作成することができません。

ちなみに、iam:PassRoleに関しては、Lambdaのみに制限されているので、EC2などにはアタッチできないようになっています。

{
   "Effect": "Allow",
   "Action": "iam:PassRole",
   "Resource": "*",
   "Condition": {
        "StringEquals": {
             "iam:PassedToService": "lambda.amazonaws.com"
        }
   }
}

IAMポリシーについて

IAMポリシーは基本的に次の4つの要素で権限をしぼっていきます。

要素 内容
Effect Allow または Deny を指定してこのポリシーを許可するのか拒否するかを設定
Action 許可(拒否)したいサービスと操作を設定
Resource どのリソースに対してActionを許可(拒否)したいのかを設定
Condition タグ、送信元IPなど、細かく条件を設定

ポリシーが適用される優先順位は明示的な Deny > 明示的な Allow > デフォルト Denyとなります。

結論

リソースの命名規則を制定

IAMポリシーの作成の前に、運用面で以下の規程を決めます。
ユーザーに権限をつけるフローに沿って、以下を名前の先頭につけるように命名規則を作りました。今後ユーザー周りの権限を作る際には、以下の命名規則に則ってリソースに名前をつけるようにします。

  • ①IAMポリシー:user-policy-
  • ②IAMロール:assumerole-
  • ③IAMポリシー:assumerole-policy-

IAMポリシー

上記、命名規則を踏まえて作成したIAMポリシーはこちらです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:CreatePolicy",
                "iam:ListPolicies",
                "iam:CreateRole",
                "iam:AttachRolePolicy",
                "iam:PutRolePolicy",
                "iam:CreatePolicyVersion"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Deny",
            "Action": "iam:AttachRolePolicy",
            "Resource": "arn:aws:iam::<アカウントID>:role/assumerole-*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Deny",
            "Action": "iam:CreatePolicyVersion",
            "Resource": [
                "arn:aws:iam::<アカウントID>:policy/user-policy-*",
                "arn:aws:iam::<アカウントID>:policy/assumerole-policy-*"
            ]
        }
    ]
}

明示的なDenyで、ユーザーの権限に使っているリソースの編集はできないようになりました。ここで上記で制定した命名規則が役に立ってきます。優先順位の関係で、Denyで指定しているリソース以外のロールの更新、ポリシーの更新はできるというようになります。

ここで問題が

上記のように、改変されたくないリソースの命名規則を決めて、そのリソースを改変できないようにするだけでは、adminポリシーをアタッチしたIAMロールを作ってしまえば、なんでもできてしまいます。そうなると、IAMのアクションを含むポリシーの作成をできなくしたり、既存のポリシーからもIAMの操作を含むポリシーはアタッチできないようにする必要がありそうです。

また、これだと自分とは関係ないポリシーやロールも変え放題ですね。複数のプロジェクトが混在しているAWSだと、命名規則を設定して、このプロジェクトに関するのだけ変えられるようにしないといけないです。

改めて結論

上記のIAMポリシーだと抜け道ができてしまっているので、改良が必要です。やっぱりIAM関連はインフラ管理者に依頼して作ってもらうほうが良いのかもしれないと思えてきました。

おわりに

Lambda関数を作成したい開発者に付与するIAMポリシーについて考えてみました!最初はインフラ管理者に提案するぞと意気込んで始めましたが、解決できるポリシーを見つけることができませんでした。

ただ、AWSのIAMについて勉強するいい機会にはなりました。これまでLambdaにつけるIAMポリシーはよく作ってきましたが、Lambdaにつける権限はSDKで使うアクションを追加していくだけなので、そんなに深く考えることがありませんでした。結局はLambdaに権限をつけるのと同じと言えば同じなのですが、この操作にどんな権限がいるのか考えていくのは大変な作業だなと感じました。

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

結局DynamoDBってなんやねん

前書き

DynamoDBに関してどの記事見ても、他と照らさないと結局なんやねんってなることが多かったので、
これ見れば何となく掴めるように噛み砕いた記事を書いてみました。

想定読者

  • AWS認定-DVAの取得を目指している方
    • SAAなどデベロッパー以外の資格では使わない知識だと思う
  • RDB(いわゆる一般的なDB)は知ってるけどDynamoDBはよくわからんよーな方
    • RDBと比較しての説明が主なのでRDBって何ぞって方はまずはそちらを調べてください
  • 簡単にDynamoDBってモノを知りたい
    • ほんとにざっくり記事なのでプロの方は見ちゃダメです

そもそもDynamoDBとは

形式ばった言い方をすると以下

Amazon DynamoDB は、高速で予測可能なパフォーマンスとシームレスなスケーラビリティを実現する、完全マネージド型の NoSQL データベースサービスです。
(引用元(公式):https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Introduction.html)

ざっくり言えば、

  • AWSが管理しているDB
  • NoSQL型のDB

という感じです。
ちなみにNoSQLとは、その名の通りSQLを使わないって解釈で良いと思います。
(正確な定義は割愛)

ざっくりデータ構造

最初にざっくり構造図を示します。以下のユースケースで設計するとします。

例:
サービス内に複数提供しているソーシャルゲームの戦績情報

属性

  • user_id:ユーザー毎に一意の値
  • game_id:サービス内のゲームの種別
  • clear_flag:クリアしたか否か

これをDynamoDBで管理すると以下。

DynamoDB図解.png

初見の方は
なんでテーブル複数あるん?
表形式で表せばええんちゃう?
ってなられていると思うので、解説していきます。

各部名称

各名称と、それが指すモノを、

  • RDBと紐付けられるモノ
  • DynamoDB独自の概念

に分けて解説していきます。

RDBと紐付けられるモノ

前提として、厳密に同じ意味ではないので注意してください。(あくまで例え)

RDB:データベース→DynamoDB:AWSアカウント(?)

?としている理由はユースケースによって変わるかなと思ったからです。

というのも、1つのAWSアカウントに対して、複数のテーブルを作成できます。(1つのリージョンに最大256個、上限は申請で緩和可能)
通常各サービス毎にAWSアカウントは分けると思っていますが、もし1つで複数サービスを運用する場合は意味合いが変わるかなと思いました。

また、テーブル毎にIAMでアクセス制限をかけられるので、若干別物な気もしますが、イメージはこのような形かと思います。

RDB:テーブル→DynamoDB:テーブル

DynamoDB図解-Page-2.png

そのままですね。
なんか複数のテーブル囲っているように見えてますが、これら1つで1テーブルです
この区切りについては後述するパーティションと呼ばれるものになります。

RDB:レコード→DynamoDB:項目(Item)

DynamoDB図解-Page-3.png

レコードに相当するものは項目(英語表記:Item)と呼ばれます。

RDB:カラム→DynamoDB:属性(Attribute)

DynamoDB図解-4.png

カラムに相当するものは属性(英語表記:Attribute)と呼ばれます。

以下はDynamoDBと言うよりNoSQLの特徴になると思いますが、
大きな違いは、1つの項目に対して自由に属性を格納できると言うことです。
何言ってるのかと言うと、例えばuser_id:1、game_id:3の情報に関しては追加の情報を入れたりとかできたりします。

DynamoDB図解-Copy of 4.png

独自の概念

以下はDynamoDB独自の概念です。

パーティション

DynamoDB図解-6.png

DynamoDB独自の仕様となります。
内部的には1つのテーブルに対して、パーティションと呼ばれる区切りに自動で分けてデータが保存されます。

どのように区切っているかというと、後述するパーティションキーという値のハッシュ値で決定されます。

なんで分けているかと言われると、そういう仕様なので目を瞑りましょう。
(パッと見クリティカルな答えはなかったのであくまで仕様という認識)

パーティションキー

DynamoDB図解-7.png

どのパーティションに格納するかを決めるための属性。
テーブル作成時に必須。
AWS推奨のベストプラクティスに則ると、多様な値が入りうる属性を指定すると良いです。
(何故なら1つのパーティションに偏っちゃうから、偏るとパフォーマンスが落ちる)

単純に、主キーと捉えてユーザーIDとかを入れるイメージで良いかと。

例では、user_idをパーティションキーとしています。

ソートキー

DynamoDB図解-7 (1).png

同じパーティションキーを持つデータ位置を近づけるために使われる属性。
設定するかは任意。
また、データ取得時に条件を絞るためにも使われる。

プライマリーキー

RDBでいうところの主キー複合キーという解釈で良いと思います。
テーブル作成時に設定するモノで、以下の2種類から構成可能です。

  • パーティションキーのみ
  • パーティションキー + ソートキー

簡単にいうと、何を元に一意の項目とするかを決めるための設定です。

(捕捉)パーティションキーとソートキーの意義

仕様的な説明だけだと、なんでこんなの設定するん?となると思うので捕捉します。

大前提、DynamoDBはパーティションキー及びソートキー以外に沿った個別検索ができないです。
もっと言うと、以下の条件に沿ったデータのみ取得可能です。

  • パーティションキーの一致
  • パーティションキーで絞ったデータに対しての、ソートキーの範囲検索
  • テーブル全部(←キャパオーバーになりうるので基本NG)

例で例えると、

  • 1人のユーザーの情報のみ
  • 1人のユーザの、特定のゲームの情報
  • 全ユーザの全情報

のみしか一度に取得出来ないのです。

他の条件に沿ったデータ取得に対応するための仕様(※)は存在しますが、
まずは、上記を考慮した設計となることは認識した方が良いです。

(※)ローカルセカンダリインデックスグローバルセカンダリインデックスと言うモノ

DynamoDBの機能(一部)

より開発者向けかもしれませんが、独自に備わっている機能を一部紹介します。

操作のためのAPIが用意されている

RDBだと接続したりとか面倒な操作があるイメージですが、DynamoDBはAPI叩くだけで操作が可能です。
もちろんSDKなども用意されています。
セキュリティ類はIAMなどで管理する感じです。

変更をトリガーに別AWSサービスを実行する(DynamoDBストリーム)

これを使うと、余計なコーディングをせずGUI上の設定のみで、DynamoDBで起きた変更をトリガーに別のAWSサービスを実行することが可能です。
例えば、ある項目が更新された際に、それをトリガーにLambdaを走らせたい、など。

LSIとGSI

それぞれローカルセカンダリインデックスグローバルセカンダリインデックスと呼ばれるモノです。
チラッと書きましたが、パーティションキーやソートキー以外で検索させたいときに使用するモノです。
詳細な仕様はこの記事の趣旨とブレるので、需要あったら別記事で書こうかなと。

ここでは、パーティションキーやソートキー以外で検索させることもある程度は可能にできる、と言うことを認識いただければと。

メリットとデメリット

結局気になるのはここだと思うので、RDBと比較したときの利点や欠点で思いついたものを記してみます。

メリット

  • AWS管理のサービスなので、サーバやバージョンなどの管理が不要(いわゆるサーバレス)
    • ただし、書き込みや読み込み時のキャパシティは自分で検討、設計する必要がある
  • 容量無制限
  • スケーリングの設定が簡単
  • バックアップや障害対策の設定が簡単
  • 独自の設計を豊かにしてくれる機能が備わっている
  • NoSQLのメリットを継承(こちらは別途NoSQLで調べると良いかもです)

など

クラウドならではのメリットをふんだんに盛り込んでいる印象です。

デメリット

  • 学習コスト
    • 多分一番のネックがここ、RDBと整合性がほぼないので基本0からの学習が必要
  • 検索の制限
    • つまり集計などには向かないです
    • 解決策にLSIとGSIを記しましたが、乱用出来る代物ではないので補佐的な位置づけで見た方が良い
  • AWSのサービスであること
    • AWSを使用できない、したくない場合は厳しい
    • AWS外で動いているサービスと連携できる(と思うが)、例えばGCPなど別クラウドを使っていてピンポイントでDynamoDBのみ使用するのはアーキテクト的に微妙な気もします

など

後書き

さらに踏み込んだ仕様に関しては、他の有識者の方の記事を見るととても参考になります。
当記事をDynamoDBの理解への踏み台にしていただけたら幸いです。

分かりにくい箇所などあればご意見いただけると。

参考記事

以下、Udemyのハンズオン講座
とても参考になるが、全て英語での説明、表記となる。(一応字幕は付けられます)

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

【AWS】EC2へのデプロイ時にmimemagicのバージョンエラーが出た場合の対処

$ bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java.
Fetching gem metadata from https://rubygems.org/.........
Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

$ rake secret
Could not find rake-13.0.3 in any of the sources
Run bundle install to install missing gems.

bundle update mimemagic

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

Amazon ECR とは

勉強前イメージ

Elastic container なんとか・・かな?
Docker的なやつ?

調査

Amazon ECR とは

Elastic Container Registry の略で
Dockerのコンテナイメージを保存しておくためのレジストリで、
Dockerコンテナイメージを保存・管理・デプロイが簡単に出来ます。
ECSやEKSにも統合されています。

特徴

  • フルマネージドレジストリ

フルマネージド型なので、
コンテナレジストリを動かすためのインフラや運用、スケーラブルなど不要になります。

  • 安全に共有およびダウンロード

コンテナイメージはHTTPS経由で転送され、保管時には自動的に暗号化されます。
またIAMを使用してイメージへのアクセス制御を行えるようにポリシーの設定が出来るため細かく設定できます

  • 高可用性や耐久性

コンテナイメージはs3に保存され、冗長化されるので安心して利用できます。

  • デプロイワークフローを簡素化する

ECSやEKSに統合されているため、開発から稼働まで簡単に数sメルことが出来ます。
開発で使用していた同様のコンテナイメージを本番用にデプロイすることが出来ます。

ECRのコンポーネント

  • レジストリ

AWSアカウントごとに用意されており、
レジストリ内にイメージリポジトリを作成し、イメージを保存します。

  • 認証トークン

イメージをpushまたはpullするにはECRレジストリに対して認証が必要です。

  • リポジトリ

ECRのイメージリポジトリには、Dockerイメージ、OCIイメージなどが含まれます。

  • リポジトリポリシー

リポジトリポリシーを使用して、イメージへのアクセス権を制御します。

  • イメージ

コンテナイメージをpushまたはpullします。
ECSやEKSで使用することが出来ます。

勉強後イメージ

実際に触ってないからあまりイメージ沸かなくて難しいけど、
Dockerイメージを保存しておく場所って感じかな?
yumリポジトリ的な?合ってる?

参考

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

AWS Fault Injection Simulator のSystemsManagerの実験を試してみた

概要

AWSからカオスエンジニアリングサービス「AWS Fault Injection Simulator」の中の
SystemsManagerを利用した実験を実施してみたいと思います。

ざっくり、AWS FISについてのおさらい

利用可能リージョン

大阪と中国の2つのリージョンを除く全てで利用可能です。

料金

使用したアクション分ごと

実験可能なサービスとアクション

サービス アクション
EC2 起動
停止
終了
ECS コンテナインスタンスのドレインステータスへの変更
コンテナインスタンスのドレインとは
EKS ノードグループインスタンスの削除
RDS フェイルオーバー
再起動
SystemsManager CPU負荷
タスク終了
メモリ負荷
ネットワーク遅延
フォールトインジェクションアクション inject-api-internal-error
inject-api-throttle-error
inject-api-unavailable-error
アクションを待つ wait

実験開始

今回は、SystemsManagerを利用した実験をやってみたいと思いますが、
細かいところはチュートリアル等々で記載されているため割愛してあります。

前提条件

ターゲットインスタンスにSSMエージェントがインストールされて利用可能になっていること

参考

実験テンプレートの作成

マネコンで「実験テンプレートを作成」を選択します。
説明、事前に作成したIAMロールを選択します。

アクション

名前を入力し、アクションタイプから
「AWSFIS-Run-Network-Latency」を選択する
ターゲットはAWSFISが自動で作成されたものを選択する

documentArnは、変更なし
documentParametersに以下を入力(入力値の説明は後述)

{"DelayMilliseconds": "200", "Interface": "eth0", "DurationSeconds":"60", "InstallDependencies":"True"}

documentVersionは、何も入力しない
durationは、「5」を入力して、保存ボタンを押します。

awsfis_003.png
AWSFIS-Run-Network-Latencyのパラメータ説明
サービス アクション デフォルト
DelayMillisecond インターフェースに追加する遅延(ミリ秒単位) 200
インターフェース 遅延するインターフェース eth0
DurationSeconds ネットワーク遅延テストの期間(秒単位) 60
InstallDependencies trueに設定すると、SystemsManagerは必要な依存関係をターゲットインスタンスにインストールします true
参考

ターゲット

AWS FISが自動的に作成したターゲットがあるため、そちらを編集します。
名前はそのままで、リソースタイプに「aws:ec2:instance」が選択されていることを確認します。
ターゲットメソッドで、リソースIDを選択し、ターゲットにするインスタンスIDを設定、
選択モードはすべてのままで、保存ボタンを押します。

awsfis_004.png

停止条件、タグは何も設定せずに実験テンプレートの作成を押します。

実験開始

実験を開始し、しばらく待ちます。

awsfis_005.png
awsfis_018.png

実験中のPing画面

ネットワークが遅延しているのが確認出来ます。

awsfis_010.png
awsfis_011.png

実験停止

次に、実験を停止の挙動をみてみたいと思います。

実験テンプレート一覧へ戻り、実験テンプレートを選択し、実験を開始します。
実験が開始されたら、アクションの「実験の停止」を選択します。

awsfis_006.png

実験中のPing画面

ネットワークの遅延が発生していることが確認できました。
その後、停止を実行して、少し遅れて止まったことが確認出来ました。

awsfis_012.png

実験テンプレートの更新

次に「duration」の値を変更した場合にどのような挙動をするのか見てみたいと思います。
「duration」を変更するため、テンプレートを更新します。

アクションの「更新」を選択
アクションのセクションから、該当のアクションを編集します。
durationを「5」から「10」に変更して、保存、テンプレートの更新を押します。

awsfis_013.png

実験の再開

再度、実験を開始してみます。
ステータスが「pending」から、「Completed」になったら、実験終了です。

awsfis_014.png
awsfis_015.png

実験中のPing画面

ネットワークの遅延が発生するタイミングは、
durationが「5」と「10」の場合で体感的に違いは感じませんでした。

awsfis_010.png
awsfis_011.png

所感

自前で実装しているようなところがAWSのマネージサービスを利用して試験が可能となり、
かつAWSへの申請も不要?となるのであれば、利用用途はあるのではないかと思いました。

今後も色々な機能が追加されるとのことなので、引き続き、追っていきたいサービスだと思いました。

誰かの参考になれば幸いです。

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

API Gateway + Lambda Authorizer + Lambdaプロキシ統合 + AWS SAM CLIを組み合わせたときのCORS設定

表題の通りです。苦しめられたので解決方法を共有します。

ポイントとしては、

ハマりどころとしては、

  • Corsプロパティを定義する際、AWS::Serverless::ApiのAuthプロパティで、AddDefaultAuthorizerToCorsPreflightをfalse(デフォルトでtrue)にしない限り、プリフライトのOPTIONSメソッドにまでLambda Authorizerが介入してしまう
    • プリフライトの送信内容はこちらで制御できないため、ほぼ確実にAuthorizerで弾かれてしまう
    • 弾かれたときにAccess-Control系ヘッダーを返しても、ステータスコード200以外は認められない
    • Lambda AuthorizerでOPTIONSメソッドのときだけ無条件で200を返してもいいが、AddDefaultAuthorizerToCorsPreflightをfalseにした方が色々スマート

一応サンプルのtemplate.ymlを残しておきます。

template.yml
  ResourceApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: v1
      Cors:
        AllowMethods: "'GET'"
        AllowHeaders: "'authorization'"
        AllowOrigin: "'*'"
        MaxAge: "'180'"
      GatewayResponses:
        DEFAULT_4XX:
          ResponseParameters:
            Headers:
              Access-Control-Allow-Methods: "'GET'"
              Access-Control-Allow-Headers: "'authorization'"
              Access-Control-Allow-Origin: "'*'"
              Access-Control-Max-Age: "'180'"
        DEFAULT_5XX:
          ResponseParameters:
            Headers:
              Access-Control-Allow-Methods: "'GET'"
              Access-Control-Allow-Headers: "'authorization'"
              Access-Control-Allow-Origin: "'*'"
              Access-Control-Max-Age: "'180'"
      Auth:
        AddDefaultAuthorizerToCorsPreflight: false
        DefaultAuthorizer: LambdaRequestAuthorizer
        Authorizers:
          LambdaRequestAuthorizer:
            FunctionPayloadType: REQUEST
            FunctionArn: !GetAtt checkTokenFunction.Arn
            Identity:
              Headers:
                - Authorization
              ReauthorizeEvery: 0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

本番環境でS3に保存されない件について

本番環境にデプロイしたときにAWSのS3に保存されずに、アプリケーション上に保存されてしまう件について改善したので記述しておきます。

どこが間違っていたのか

config/development.rb
# 上記省略
  config.active_storage.service = :amazon
config/production.rb
# 上記省略
  config.active_storage.service = :local

間違っていたのは上記の記述で、開発環境ではS3に保存できるように記述を変更していたのですが本番環境でlocalに保存されるようになっていました。
production.rbには本番環境での設定、development.rbでは開発環境での設定を記述しているので漏れのないように変更加えておかないとな、、と反省いたしました。
なのでproduction.rbの記述を下記のように変更。

config/production.rb
# 上記省略
  config.active_storage.service = :amazon

これで開発環境と同様にS3に保存できるようになりました。

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

【Cloud9】failed to write new configuration file /home/ubuntu/.gitconfig.lock

Error

Rails チュートリアルをやるために Cloud9 を開いたところ下記のエラーが発生。
Tab 補完が効かないなど変な挙動になった。

error: failed to write new configuration file /home/ubuntu/.gitconfig.lock

┌──────────────────────────────────────────────────────────┐
│                 npm update check failed                  │
│           Try running with sudo or get access            │
│           to the local update config store via           │
│ sudo chown -R $USER:$(id -gn $USER) /home/ubuntu/.config │
└──────────────────────────────────────────────────────────┘

Translate

failed to write new configuration file /home/ubuntu/.gitconfig.lock

新しい設定ファイルの書き込みに失敗しました /home/ubuntu/.gitconfig.lock

Resolve

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
・
・
・
/dev/xvda1      9.7G  9.7G     0 100% /

ディスク容量が圧迫されて書き込み処理等の操作が正しく行われなくなっていると予想。
今まで作成した別のアプリの削除を実施したところ、元の挙動に戻った。

xxx:~/environment $ rm -rf hello_app
xxx:~/environment $ rm -rf toy_app
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

現場未経験者がAWS SAPを取得するまでにやったこと【合格体験記】

目次

1.はじめに
2.筆者のスペック
3.勉強期間と本番スコア
4.勉強に使ったもの
5.勉強の過程を詳しく
6.受験の感想とアドバイス
7.まとめ
8.【おまけ】SAP勉強して良かったこと

1. はじめに

エンジニア歴1ヶ月ですが、スキルをアピールしてAWSの現場に入りたいという動機から、AWS SAP(ソリューションアーキテクト-アソシエイト)を取得しました!
現場未経験でプロフェッショナル資格を取るのは我ながら結構レアなケースだと思ったので、取得までの勉強法を共有してみます。主に、同じ駆け出しエンジニアの方のご参考になればと思います!

2. 筆者のスペック

・取得済み資格:AWS SAA, DVA
・SES企業のインフラエンジニア
初現場配属から1ヶ月にも満たない、完全なる駆け出しエンジニア
・AWSの現場は未経験

3. 勉強期間と本番スコア

勉強期間

2ヶ月ちょっと
SAP対策にかけた時間は約150~160時間

スコア

789点(合格ライン750点)
まさかの一発合格!!しかしギリギリでした。。
SAP本番結果.png

4. 勉強に使ったもの

使った教材

SAP本
Udemy模試
AWS Web問題集で学習しよう

その他よく参考にした資料

・Blackbelt
・AWS公式ドキュメント
・Classmethod記事等、技術ブログ

またSAP対策というより、単なる興味から以下の本を使ったLambdaのハンズオン学習をしました。
AWS Lambda実践ガイド

5. 勉強の過程を詳しく

1. SAP本(25h)

まずは、SAP本を買って後半の問題集と模試を解きました。(前半の各サービスの説明は、かなりさらっとしているため読んでないです)
解説がかなり丁寧で図が適度に入っており、分かりやすくて良かったです…!!

アソシエイト合格時にはふわっとしか分かってなかった箇所がちゃんと理解できました。
SAP取る予定なくても、AWSの基本サービスの理解を深めるたい方には最適な教材だと思います。

2. Udemy模試1周目(75h)

1.の模試を解き終わった後は、Udemyの模試5回分を一周しました。
ここに一番時間を使いました。。
以下を繰り返しました。

・問題を通しで解く

 試験本番に慣れるため、なるべく途中休んだりせず解きました。

・解説をしっかりと読む、ググる!

 Udemyの解説はSAP本と比べると分かりづらいですが、がんばって読みました。
 それでも理解できない箇所は、
  ・BlackeBeltを視聴してサービスの全体像を把握したり、
  ・Classmethod等の分かりやすい記事を見たり
 して勉強しました。

・知らなかった内容はノートにまとめる(おすすめ!)

 上で解説理解してもすぐ忘れます。なので復習用に自作ノートを作りました。
 自分の場合Macのメモ帳に、図も貼り付けながら自分の言葉でまとめていきました。後に問題解いてて再度つまづいた時に、復習しやすく知識の定着になったのでかなり有用。DVA対策時も作ってましたが、その時と合わせると計80枚くらい作りました。
 こんな感じです↓
SAP対策ノート.png

3. 公式模試受験

理解度把握するために、このタイミングで受けました。
スコアは、なんと80%と予想外に高得点。
SAP公式模試結果.png

しかしながら体感では6割くらいだったので、さらに勉強しました。

4. 『AWS Web問題集で学習しよう』1周(30h)

問題数をこなすために、ダイヤモンド会員を契約して378問(7問×54セット)解きました。
ここで、1セット解き終わる毎に「4.Udemy模試」で作った自作ノートも参考にしつつ解説を見直し、知識を定着させていきました。
ここまできても正答率50%くらいでしたが、あまり気にせずどんどん解きました。

5. Udemy模試2周目(25h)

最後の締めとして、試験直前の3日間でUdemy模試5回分を解きました。
しかしながら、直前なのにどの模試も正答率60%程度と、不安しかない状態で受験しました。。
もうSAPの勉強に飽きてたので、「ええい、いったれ!」とそのまま受験しました?

6. 受験の感想とアドバイス

感想

1問2分ペースで解き2時間半で解き終わり、残り30分でフラグをつけた問題(約20問)の半分しか見直せませんでした。。
ちょいちょいかなり難しい問題があり、そこについ時間多めに使ったのがまずかったかと。。
結果オーライでしたが、時間配分はもっと気をつけるだったと反省。

アドバイス

集中力の維持が一番大事!!思ったより問題が難しく、序盤から「落ちたかな。。」と正直思ってましたが、諦めず3時間集中して解き切りました。
 また、思考力のある午前中での受験が、個人的にはおすすめです。
『AWS Web問題集で学習しよう』と類似した問題が結構出ました(全体の2~3割くらい?)。確実に合格するには、これを何周かするのが得策かもです。私も一発合格できるとは思ってなかったので、さらに勉強するならWeb問題集を何周かしてたと思います。
・SAP学習の手始めとしては、SAP本が個人的におすすめです。解説がわかりやすく、AWSの知識が体型的に身につくし、挫折しづらいです。

7. まとめ

AWS SAPはアソシエイトと違い、暗記ではなく根本的なサービスの理解度が高くないと合格できないです。
問題をたくさん解くことも大事ですがそれ以上に、見直しに時間をかけて、各サービスの役割・ユースケース・他サービスとの組み合わせ等きちんと理解することが大事です。
(私も、勉強時間の7割くらいは見直しに使ってました!)
長くなってしまいましたが、どなたかのご参考になればと思います!

8. 勉強して良かったこと

・公式ドキュメントが(少しずつ)読めるようになってきた。

あの分かりづらい公式の文章をずっと敬遠してましたが、、対策しているとドキュメントにしか情報がないこともあったため読むようになり、徐々に抵抗がなくなりました。一次情報にアクセスできるのって大事ですよね。

・マネジメントコンソールの変更に慌てなくなった。

技術書やブログ等を参考にハンズオンしていると、執筆当時のものとコンソール画面が変わっていることはよくあり、度々引っかかっていましたが、
一つひとつの設定値やオプションを理解できるようになったため慌てなくなりました。

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