Open GitEngHar opened 1 year ago
サイト仕様
こんなデザインにしたい (最終的には)
こちらでStep2~3は実施済み
以下を実施してみる
■ ref : "【ポートフォリオをECSで!】Rails×NginxアプリをFargateにデプロイするまでを丁寧に説明してみた(VPC作成〜CircleCIによる自動デプロイまで) 前編 - Qiita" "https://qiita.com/maru401/items/8e7d32a8baded045adb2#41-%E3%82%BF%E3%82%B9%E3%82%AF%E3%81%AE%E4%BD%9C%E6%88%90"
■ ref : "MySQL DB インスタンスの作成と接続 - Amazon Relational Database Service" "https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.MySQL.html"
RDSのmysqlデータベースのsgで許可しているsgをEc2に割り当て、mysqlに接続する
とりあえず成功
試しの実施で以下のコードでtest
import pymysql
connection=pymysql.connect(host='<endpoint>', port=3306, user='<user>', password='<pass>', db='<dbname>')
try:
with connection.cursor() as cursor:
sql = "select * from sampletable"
cursor.execute(sql)
result = cursor.fetchall()
print(result)
finally:
connection.close()
セキュアな通信は以下より実施できそう
IAM 認証および AWS SDK for Python (Boto3) を使用した DB インスタンスへの接続 - Amazon Relational Database Service
ここで気づいたが、Fargate複数立てる必要ないのでは..??
資格の登録をする場合、json形式で送ったデータをmysqlに書き込めるようにしたい
→ backendはpython or Go or js でリクエストを待ち受けるものにしたい
これを作っていくZO!
環境どうしよう ☆cloud9 (pythonが最初からある)mysqlはDocker環境で構築しよう☆
やること
curlで指定しているURLは現在は固定だが、タスクとして実行した際は変動する。そこをどうするかは悩みどころ。 kubernetesではingressにresponseを送ることで何とかなっていたので同じような構成にしたい。
socket通信でjson形式のデータをやり取りできたが、受け取ってる文字コードをdecodeして、テキストとして読み込んでいるため、jsonとしてのデータではないと感じた。 そのため、restAPIを構築してjson形式のデータをやり取りできるようにしたい
以下を参照して実施 PythonでAPIを爆速で構築してみた - Qiita
8000でlocal公開されているapiにcurlを実施。結果が返ってきた
ec2-user:~/environment/plactis/pyServer $ curl -X POST "localhost:8000/items" -H 'Content-Type: application/json' -d '{"name":"おなまえ","price":123}'
{"item_name":"おなまえ","twice price":246.0}
/fruitsで果物一覧を取得
$ curl -X GET "localhost:8000/fruits"
{"message":"orange, kiwii, grape, lemon, "
/colorで色の一覧を取得
$ curl -X GET "localhost:8000/color"
{"message":{"pink":["aurora","begonia","camellia"],"red":["azalea","burgundy","carmine"],"orange":["apricot","carrot","nasturtium"]}}
/bmi で 体重と身長からBMIを計算する
$ curl -X POST "localhost:8000/bmi" -H 'Content-Type: application/json' -d '{"name":"haru","h":1.7,"w":70}'
{"name":"haru","bmi":24.221453287197235}
/literalnum で文字数を教えてくれるやつ
$ curl -X POST "localhost:8000/strnum" -H 'Content-Type: application/json' -d '{"string":"こんにちは。文字の長さを教えてください"}'
{"strNumber":19}
FASTAPIはPythonでAPIを構築できるwebフレームワーク Djangoと並行して人気らしい
app(/test) app(/test/calc)
上記のように複数pathごとに処理を変えられる
AWS EC2にssh後外部との通信がうまくいかず、packege更新が一生終わらなかったため、 Docker Playground環境で実施
playgroundで必要だった環境構築
pip3 install pymysql
pip3 install fastapi
pip3 install pydantic
pip3 install uvicorn
pip install cryptography
動作確認用のコード
import pymysql
def connectDB():
connection=pymysql.connect(host='<ip>', port=3306, user='root', password='password', db='SAMPLEDB')
try:
with connection.cursor() as cursor:
sql = "select * from sampletb"
cursor.execute(sql)
result = cursor.fetchall()
return result[0][0]
finally:
connection.close()
動作確認用コマンド
#msyql docker を構築
docker run --name mysqlDB -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 -d mysql
#docker login
docker exec -it mysqlDB bash
以下を参考にデータベースをセットアップ MySQLチートシート - Qiita MySQL | CHAR型とVARCHAR型
python でクエリした結果はtuple型で帰ってくる
配列から抜き取る簡単な形式で値を取得
簡単に以下のようになる
import mysqlConnect as connect
from fastapi import FastAPI
app = FastAPI()
@app.get("/connectTest")
async def root():
uname = connect.connectDB()
return {"message": uname}
実行結果
[node1] (local) root@192.168.0.28 ~
$ curl -X GET "localhost:8000/connectTest"
{"message":"haru"}
技術種類と資格名をそれぞれに格納しておく
tableName : certtb
#mysqlログイン
$ mysql -u root -p
#database作成
$ CREATE DATABASE SAMPLEDB;
$ USE SAMPLEDB;
#table作成
$ CREATE TABLE certtb(skillType varchar(255) , certName varchar(255));
#データを試しに登録
$ insert into certtb (skillType,certName) VALUE ('Linux','LPIClv1')
#登録データの確認
$ select * from sampletb
確認結果
mysql> select * from certtb;
+-----------+----------+
| skillType | certName |
+-----------+----------+
| Linux | LPIlCv1 |
+-----------+----------+
backend.py
import mysqlConnect as connect
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
skilltype: str
certname: str
@app.get("/getcert")
async def root():
connect.viewDB()
return {"message": "OK"}
@app.post("/regcert")
def update_item(item: Item):
connect.regDB(item.skilltype,item.certname)
return {"rsponse": "OK"}
mysqlConnect.py
import pymysql
connection=pymysql.connect(host='192.168.0.18', port=3306, user='root', password='password', db='SAMPLEDB')
def viewDB():
print("!!!!!view function")
sql = "select * from certtb"
queryDB(sql)
def regDB(skillType,certName):
sql = "insert into certtb(skillType,certName) VALUE ('{0}','{1}')".format(skillType,certName)
print("exec : {0}".format(sql))
queryDB(sql)
def queryDB(sql):
try:
with connection.cursor() as cursor:
cursor.execute(sql)
result = cursor.fetchall()
print(result)
return 0
finally:
connection.close()
上記で localにmysql databaseが立っており、uvicorn backend:app --reload
でpython APIも動作していれば、以下実行結果が得られる
#資格情報を登録する要求 (OK が帰ってくれば登録できている 多分)
$ curl -X POST "localhost:8000/regcert" -H 'Content-Type: application/json' -d '{"skilltype":"Cisco","certname":"CCNA"}'
{"rsponse":"OK"}
$ curl -X GET "localhost:8000/getcert"
{"message":"OK"}
#Server側で取得したクエリデータを取得出きている(要改善)
(('Linux', 'LPIlCv1'), ('Linux', 'LPIClv2'))
取得した値をjson形式に成型
import mysqlConnect as connect
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
def createRespons(result):
response = ""
for i in range(len(result)):
data = "skilltype:{0},certcame:{1}".format(result[i][0],result[i][1])
response += "{" + data + "}"
if len(result)-1 != i : response += ","
response = "{" + response + "}"
return response
class Item(BaseModel):
skilltype: str
certname: str
@app.get("/getcert")
async def root():
result = connect.viewDB()
return createRespons(result)
@app.post("/regcert")
def update_item(item: Item):
connect.regDB(item.skilltype,item.certname)
return {"rsponse": "OK"}
実行結果
$ curl -X GET "localhost:8000/getcert"
"{{skilltype:linux,certcame:LPIC1},{skilltype:linux,certcame:LPIC2}}"
backend.py
import mysqlConnect as connect
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
def createTupleRespons(result):
response = ""
print(result)
for i in range(len(result)):
data = "skilltype:{0},certname:{1}".format(result[i][0],result[i][1])
response += "{" + data + "}"
if len(result)-1 != i : response += ","
response = "{" + response + "}"
return response
def createJsonResponse(result):
print(type(result))
data = "{0}".format(result)
response = data.replace("[","{")
response = response.replace("]","}")
return response
class Item(BaseModel):
skilltype: str
certname: str
@app.get("/getcert")
async def root():
result = connect.viewDB()
if type(result) is tuple:
response = createTupleRespons(result)
elif type(result) is list:
response = createJsonResponse(result)
return response
@app.post("/regcert")
def update_item(item: Item):
connect.regDB(item.skilltype,item.certname)
return {"rsponse": "OK"}
mysqlConnect.py
import pymysql
def viewDB():
sql = "select * from certtb"
sendSql = "{0}".format(sql)
return queryDB(sql)
def regDB(skillType,certName):
sql = "insert into certtb(skillType,certName) VALUE ('{0}','{1}')".format(skillType,certName)
queryDB(sql)
def queryDB(sql):
connection=pymysql.connect(host='192.168.0.8', port=3306, user='root', password='password', db='SAMPLEDB', cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
cursor.execute(sql)
connection.commit()
result = cursor.fetchall()
print(result)
return result
finally:
connection.close()
実行結果
[node1] (local) root@192.168.0.8 ~
$ curl -X POST "localhost:8000/regcert" -H 'Content-Type: application/json' -d '{"skilltype":"cisco","certname":"CCNA"}'
{"rsponse":"OK"}[node1] (local) root@192.168.0.8 ~
$ curl -X GET "localhost:8000/getcert"
"{{'skillType': 'linux', 'certName': 'LPIC1'}, {'skillType': 'linux', 'certName': 'LPIC3'}, {'skillType': 'linux', 'certName': 'LPIC2'}, {'skillType': 'cisco', 'certName': 'CCNA'}}"
ユースケースは RDS で使われること osで設定したい環境変数一覧
Code
import pymysql
import os
tbname = os.getenv("TB_NAME")
dbname = os.getenv("DB_NAME")
mysqlUser = os.getenv("MYSQL_USER_NAME")
mysqlUserPass = os.getenv("MYSQL_USER_PASSWORD")
mysqlEndpoint = os.getenv("MYSQL_ENDPOINT")
def viewDB():
sql = "select * from {0}".format(tbname)
return queryDB(sql)
def regDB(skillType,certName):
sql = "insert into {0}(skillType,certName) VALUE ('{1}','{2}')".format(tbname,skillType,certName)
queryDB(sql)
def queryDB(sql):
print("tbname:{0},dbname:{1},mysqlUser:{2},mysqlUserPass:{3},mysqlEnd:{4}".format(tbname,dbname,mysqlUser,mysqlUserPass,mysqlEndpoint))
connection=pymysql.connect(host=mysqlEndpoint, port=3306, user=mysqlUser, password=mysqlUserPass, db=dbname, cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
cursor.execute(sql)
connection.commit()
result = cursor.fetchall()
return result
finally:
connection.close()
terminal
[node2] (local) root@192.168.0.17 ~
$ export TB_NAME=certtb
[node2] (local) root@192.168.0.17 ~
$ export DB_NAME=SAMPLEDB
[node2] (local) root@192.168.0.17 ~
$ export MYSQL_USER_NAME=root
[node2] (local) root@192.168.0.17 ~
$ export MYSQL_USER_PASSWORD=password
[node2] (local) root@192.168.0.17 ~
$ export MYSQL_ENDPOINT=192.168.0.8
実行結果は上記同様なので割愛だが、できた🎉
Docker File
from <python image>
cp アプリケーションたちとrequier.txt
pipでライブラリをそろえる
uvicornで起動※errorが出れば環境変数を設定しよう
docker上のアプリにlocalhostでアクセスしたらERR_EMPTY_RESPONSEが出る - Qiita
コンテナをlocalhost:portでlistenさせると、リクエストが届かない問題。 ホスト→コンテナネットワーク→コンテナ 上記のようなネットワークになる際、コンテナネットワークからコンテナへのリクエストはコンテナ内のアドレスに返還されるため、厳密にlocalhostのアドレス(127.0.0.1)ではない。そのため、リクエストが届かない。
■ リクエストが届いていない
■コンテナ情報
ec2-user:~/environment $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5a2d9fdf5259 pyapiserv:v1 "uvicorn backend:app…" 9 minutes ago Up 9 minutes 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp pyserv2
31263ae8c5f5 mysql "docker-entrypoint.s…" 3 hours ago Up 18 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysqlDB
解決! listen ipaddress が localhostとなっていたので、0.0.0.0に変更して成功 Internal Server Error で、サーバの処理失敗となっており、到達はした🎉
サーバの処理失敗はネットワークが異なることによる到達しないエラーかと思われる
■ get成功
ec2-user:~/environment $ curl -X GET "localhost:8080/getcert"
"{{'skillType': 'cisco', 'certName': 'CCNA'}, {'skillType': 'Linux', 'certName': 'LPIClv1'}}"
■ server側もいい感じ
■ post成功
ec2-user:~/environment $ curl -X POST "127.0.0.1:8080/regcert" -H 'Content-Type: application/json' -d '{"skilltype":"Linux","certname":"LPIClv3"}'
{"rsponse":"OK"}
■ dbに反映されていることを確認
ec2-user:~/environment $ curl -X GET "172.17.0.1:8080/getcert""{{'skillType': 'cisco', 'certName': 'CCNA'}, {'skillType': 'Linux', 'certName': 'LPIClv1'}, {'skillType': 'Linux', 'certName': 'LPIClv2'}, {'skillType': 'Linux', 'certName': 'LPIClv3'}}"
--ENDPOINTの指定を docker のipを指定したら成功した
ec2-user:~/environment $ docker inspect 31263ae8c5f5 | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
ここに従ってimageをpush
pushできていることを確認
無限cloud9環境リロード問題を解消した素晴らしい記事 AWS Cloud9の環境作成が一生終わらない件について - Qiita
整え終わって..
できたー🎉
■ GET
■ POST
POSTできていたかをGETして確認
'
で文字を囲っても文字列として認識されないので、jsonのでコードやコマンドが崩れてしまい、エラーになる
postしてリクエストを表示するだけでええや
jsでリクエストを送りたいので、fetchを利用するが HTTP通信になるため、バックエンドでhttp通信のできるAPIが必要。 現在のままだとただ、パブリックIPにリクエストを投げてるにすぎないので、HTTPでリクエストを受け付けてくれる機能が必要。 L7LBのALBを使おう。そうしよう。
Curl成功
フロント (仮) 完成🎉
コード
<!DOCTYPE html>
<html>
<body>
<p>Helloword</p>
<button id="clickEvent" onclick="getCertData();">リクエスト</button>
<p id="view"></p>
<script language="javascript" type="text/javascript">
async function getCertData(){
target = document.getElementById("view");
var viewText = ""
var response = await fetch('エンドポイント');
if(!response.ok){
viewText = "ResponseError";
}
viewText = ""
const certRawJsonData = await response.json();
const certJsonToStringData = JSON.stringify(certRawJsonData)
var fixCertJsonTypeData = certJsonToStringData.replace(/'/g,"\"")
fixCertJsonTypeData = fixCertJsonTypeData.replace("\"{{","[{")
fixCertJsonTypeData = fixCertJsonTypeData.replace("}}\"","}]")
const certJsonData = JSON.parse(fixCertJsonTypeData,(key,value) => {
switch(key){
case "skillType":
viewText += `資格スキルの種類 : ${value} <br>`;
break;
case "certName":
viewText += `資格名 : ${value} <br><br>`
}
})
target.innerHTML = viewText;
console.log('response.json():', viewText);
}
</script>
</body>
</html>
作成したが、サービスをプライベートかつ、パブリックIPを無効化していたら、ECRのImageがpullできなかった ECRのエンドポイントが作成されていないため、外部からのpullで取得している
調べたら S3 や Fargate や ECRのエンドポイント等々面倒くさいので、今回は ECS
をパブリックにして、ALBはプライベートにして実施する
→ ALBを内部通信にすると、sgが難しい。パブリックでwebサーバを公開しているとwebサーバからのリクエストもパブリックになっている印象。そのため、sgでwebに適用されているsgを指定しても通らない
Image化してFargateで動作できた🎉 次項を参照
GET できた🎉 ちなみにFargateで公開しているタスクのパブリックIPに直接入っている
POSTまで行きたい
ローカルからALBへのリクエストで cors
ブロックが悲しいTT
corsエラーはヘッダーに許可するブラウザのドメインを記述して回避できた
そもそも、信頼できないドメインからの受信を拒否する ブラウザ の機能でエラーが出ていたと思われる
クロスドメインだったような気がする
FastAPIでCORSを回避 - Qiita
ヘルスチェック失敗 アプリケーション側で "/" をlistenさせておらず エラーが出続けていた その影響なのか、サービスが削除され ALB も同様に削除されていた ヘルスチェックパスである "/" でListenするようにして免れた
Jsonのデータ形式に苦労 違反した掟をいかに記載
"
で囲むべし!(windowsかよっておもった)AWS内で通信を簡潔させるには各箇所にエンドポイントを作成する必要がある。
Fargateでタスクを実行してタスク間通信を行うところまで実施したので、 第3者がこの手順を追うことができるように作成しよう
CloudFormationで冪等性を保った構成をとってもよさそうだが..?? Imageが自作する必要ありなので、難しそう..??
現在の構成
目的
現在諸事情で「AWS ECS」を学ぼうとなっている
公式Docs / 本 / ブログ を参考にしながら ECR へ image を push し image を ECSでサービス として 動かし、外部からアクセスできた
その過程で より 「セキュリティ性の高い構成」 と 「実用性の高い構成」を組めるようになりたいと感じた
上記を 模擬的なビジネスユースケース を 考え 構築していくことで 実現したい
目標
物凄く リッチ な自己紹介ページを作る
何を持って達成とするか
以下項目の全てにチェックが入っていること
満たすべき技術要件
基本技術要件(やることなくなったら確認)
- [ ] AWSアカウントの設定: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] AWSのリージョンとアベイラビリティゾーン: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] EC2インスタンスの作成: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] S3バケットの作成: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] VPCの設定: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] セキュリティグループとネットワークACL: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] IAMロールとアクセス許可: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] データベースのセットアップ: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] モニタリングとログ: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている - [ ] コスト管理: - [ ] 触ったことがある - [ ] ドキュメントに目を通した - [ ]知らなかったことで知れたことをまとめられている[x] ECSの詳細を学ぶ:
[ ] セキュリティグループとネットワークアクセス制御:
[ ] VPCの構築:
[ ] IAMロールとアクセス許可:
[ ] モニタリングとログ:
[ ] セキュアなコンテナイメージ:
[ ] セキュリティポリシーの実装:
[x] CI/- [ ] CDの導入:
[ ] AWSのベストプラクティスの確認: