まさなみブログ

主にweb系開発の記事を適当に書いてます。

M1 Macで ECSにSpring bootのREST APIをデプロイしようとしたけど失敗した

環境

macOS Big Sur M1

デプロイするサンプルアプリ

/greetinghello worldを返すだけのSpring-bootのREST API

github.com

ECS Fageteへのデプロイ

copilotを使ってデプロイする

AWS CLIが必要なので先にインストール

# homebrewでインストール
$ brew install awscli
# 確認
$ aws --version
aws-cli/2.3.5 Python/3.9.7 Darwin/20.6.0 source/arm64 prompt/off

AWS CLIの初期設定

$ aws configure
AWS Access Key ID [None]: アクセスキー
AWS Secret Access Key [None]: シークレットアクセスキー
Default region name [None]: リージョン
Default output format [None]:

Copilotインストール

$ brew install aws/tap/copilot-cli
# 確認
$ copilot -v
copilot version: v1.12.0

Copilotで環境作成

$ copilot init
What would you like to name your application? [? for help] greeting-app #デプロイするアプリケーション名を入力

Application name: greeting-app

  Which workload type best represents your architecture?  [Use arrows to move, type to filter, ? for more help] # 構築したい構成を選びます。
    Request-Driven Web Service  (App Runner).
    > Load Balanced Web Service   (Internet to ECS on Fargate)
    Backend Service             (ECS on Fargate)
    Worker Service              (Events to SQS to ECS on Fargate)
    Scheduled Job
# LB込みでバックエンドサービスをデプロイしたいので Load Balanced Web Serviceを選択

Which Dockerfile would you like to use for greeting-app?  [Use arrows to move, type to filter, ? for more help] # Dockerfileを選択
  > greeting/Dockerfile

 Which port do you want customer traffic sent to? [? for help] (80) #ポートを選択

  Would you like to deploy a test environment? [? for help] (y/N) y # テスト環境としてデプロイするか選択

動かない..

アプリケーションのヘルスチェックで落ちて動かない。。

✔ Created ECR repositories for service greeting-app..

All right, you're all set for local development.
Deploy: Yes

✔ Linked account 680356542845 and region ap-northeast-1 to application greeting-app..

✔ Proposing infrastructure changes for the greeting-app-test environment.
- Creating the infrastructure for the greeting-app-test environment.     [create complete]  [92.1s]
  - An IAM Role for AWS CloudFormation to manage resources               [create complete]  [27.2s]
  - An ECS cluster to group your services                                [create complete]  [8.8s]
  - Enable long ARN formats for the authenticated AWS principal          [create complete]  [2.0s]
  - An IAM Role to describe resources in your environment                [create complete]  [23.8s]
  - A security group to allow your containers to talk to each other      [create complete]  [5.7s]
  - An Internet Gateway to connect to the public internet                [create complete]  [18.1s]
  - Private subnet 1 for resources with no internet access               [create complete]  [19.0s]
  - Private subnet 2 for resources with no internet access               [create complete]  [19.0s]
  - Public subnet 1 for resources that can access the internet           [create complete]  [19.0s]
  - Public subnet 2 for resources that can access the internet           [create complete]  [19.0s]
  - A Virtual Private Cloud to control networking of your AWS resources  [create complete]  [15.5s]
✔ Created environment test in region ap-northeast-1 under application greeting-app.
Environment test is already on the latest version v1.6.1, skip upgrade.
Building your container image: docker build -t 680356542845.dkr.ecr.ap-northeast-1.amazonaws.com/greeting-app/greeting-app --platform linux/amd64 /Users/masami/greeting -f /Users/masami/greeting/Dockerfile
[+] Building 2.2s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                                   0.0s
 => => transferring dockerfile: 36B                                                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                        0.0s
 => [internal] load metadata for docker.io/library/openjdk:11-jdk-slim                                                                                                                                 2.1s
 => [internal] load build context                                                                                                                                                                      0.0s
 => => transferring context: 117B                                                                                                                                                                      0.0s
 => [1/2] FROM docker.io/library/openjdk:11-jdk-slim@sha256:ad41c90d47fdc84fecb3bdba2deb38e378bbde1d7f5a378ba0964c466b23dbca                                                                           0.0s
 => CACHED [2/2] COPY build/libs/greeting-0.0.1-SNAPSHOT.jar app.jar                                                                                                                                   0.0s
 => exporting to image                                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                                0.0s
 => => writing image sha256:658315566f443e0dc2795c5d605d29ad71f4f9e512469135bc4c908425447ade                                                                                                           0.0s
 => => naming to 680356542845.dkr.ecr.ap-northeast-1.amazonaws.com/greeting-app/greeting-app                                                                                                           0.0s
Login Succeeded
Using default tag: latest
The push refers to repository [680356542845.dkr.ecr.ap-northeast-1.amazonaws.com/greeting-app/greeting-app]
408e323e553a: Pushed
007baf3afc22: Pushed
62763247decf: Pushed
2bf2b8c78141: Pushed
e8b689711f21: Pushed
latest: digest: sha256:09e11c29227fac3c7c730af1a9eb59fb7950d2003b2a984b6d7e790b452e8e94 size: 1372
✔ Proposing infrastructure changes for stack greeting-app-test-greeting-app
- Creating the infrastructure for stack greeting-app-test-greeting-app            [create complete]  [376.6s]
  - Service discovery for your services to communicate within the VPC             [create complete]  [0.0s]
  - Update your environment's shared resources                                    [update complete]  [181.1s]
    - A security group for your load balancer allowing HTTP and HTTPS traffic     [create complete]  [6.6s]
    - An Application Load Balancer to distribute public traffic to your services  [create complete]  [152.3s]
  - An IAM Role for the Fargate agent to make AWS API calls on your behalf        [create complete]  [27.6s]
  - A CloudWatch log group to hold your service logs                              [create complete]  [3.3s]
  - An ECS service to run and maintain your tasks in the environment cluster      [create complete]  [91.6s]
    Deployments
               Revision  Rollout        Desired  Running  Failed  Pending
      PRIMARY  2         [in progress]  1        1        1       0

    ✘ Latest failure event
      - (service greeting-app-test-greeting-app-Service-bfQWCpWYD0kA) (port 80
        ) is unhealthy in (target-group arn:aws:elasticloadbalancing:ap-northe
        ast-1:680356542845:targetgroup/greet-Targe-RCG2FPBWXXAQ/3d66f91ea0a3cf
        a1) due to (reason Health checks failed).
  - A target group to connect the load balancer to your service                   [create complete]  [4.0s]
  - An ECS task definition to group your containers and run them on ECS           [create complete]  [3.5s]
  - An IAM role to control permissions for the containers in your tasks           [create complete]  [23.6s]

調べたところM1 macだとデプロイで死ぬらしい(Dockerのアーキテクチャが異なるため). copilot init だと環境構築からアプリケーションのデプロイまで一括でやってくれるが、私の環境だとデプロイは別の方法で回避しないといけない。 以下コマンド等で個別にステップを実施できる。

$ copilot env init
$ copilot svc init
$ copilot svc deploy

# CodePipelineも作れる
$ copilot pipeline init
$ copilot pipeline update

アプリのデプロイはまたの機会に実施したい。

作成した環境のお片付け

# 作成したアプリケーションの確認
$ copilot app ls
greeting-app

# 削除
$ copilot app delete --name greeting-app
Sure? Yes

まとめ

起動まではいかなかったが、お手軽にWebサービスに必要な環境一式を揃えることができる便利ツールであることはわかった。
ネットワークの定義やIAM定義など、結構手間がかかることもやってくれるのは個人的にありがたい。 私が関わる案件では環境がterraformで整備されていることが多いのであまりお世話にならないが、個人でサクッとサービスを立ち上げるには打って付けだと思う。
一方で込み入った環境設定をしたい場合はCloudFormationの知識が必要となってくるので人によっては学習コストが発生する。
他にも細かい制約はありそうですが、今回はここまで。

【Laravel】eloquent を使用していて、auto increment falseが効かなかった件

記事にするほどでもないが、メモがてら

事象

eloquentだとデフォルトでidがauto incrementですが、プレフィックスを付けたくてアプリ側でidを振る必要がでてきたのでauto incrementをfalseにしたが、反映されなかった。

環境

Laravel Framework 6.5.0

原因、対応

モデルの$fillableにidいれてなかったのでアプリ側で作ったIDがDBに反映されなかった。 $fillableにidを追加すると動作した。
エラーが返してくれれば良いのにauto incrementで入れてくれるんだね。

【Vue】filepond Postパラメータ渡し方

はじめに

filepondというライブラリを使用してファイルアップロード機能を実装していたが、postパラメータをサーバー側に渡すところで少し躓いたのでメモ。

環境

vue@2.6.11
vue-cli 2.9.6 Laravel Framework 6.5.0
filepond@4.9.5

やったこと

filepond インストール

filepondの導入と基本的な使い方はいろいろと記事があるので割愛。
参考にさせて頂いたサイト
pqina.nl

reffect.co.jp

File metadata プラグインインストール

公式ページのFile metadataの項目を参考にしてインストール

npm i filepond-plugin-file-metadata --save

vueファイル ユーザーIDをPostで送る

<template>
        <file-pond
          :server="url"
          allowRevert="false"
          instantUpload="false"
        />
</template>
<script>
import vueFilePond, { setOptions } from "vue-filepond";
import "filepond/dist/filepond.min.css";
/* Plugin */
import FilePondPluginFileMetadata from "filepond-plugin-file-metadata";
const FilePond = vueFilePond(
  FilePondPluginFileMetadata
);

export default {
  components: { FilePond },
  data() {
    return {
      url: "http://localhost:8000/api/fileupload",
      userId: 1,
    }
 },  
  /** 中略 */
  mounted() {
    //ここでパラメータをセット
    setOptions({
      fileMetadataObject: {
        userId: this.userId
      }
    });
  }
    ...

サーバー側(php)

use Illuminate\Http\Request;

    public function postFileUpload(Request $request)
    {
        $input = json_decode($request->input('filepond'));
        //ユーザーID取得
        $userId = $input->userId;

        //なんらかの処理
    }

終わりに

とりあえず動くが、ファイルのメタデータとしてデータを渡している?のは違和感がある。 良い方法があれば教えていただければうれすぃ

【Laravel/Vue】postのパラメータの書き方ミスって419エラーと勘違いした話

はじめに

現在Laravel/Vue.jsでマッチングサイトを作っており、その際認証周りの経験が浅くしょうもないところで詰まってしまった話。

環境

vue@2.6.11
Laravel Framework 6.5.0
Firefox 73.0.1

起こったこと

php/laravel/vueを使ったSPAのマッチングサイトを作っており、お気に入り機能実装時にaxiosのpostメソッドを追加。
別の部分に書かれてるgetメソッドを一旦コピペしてpostに変え、パラメータを以下のように修正しました。
※コードはサンプル。

#コピペ元
    getBandList() {
      axios.get("/matching", {
          params: {
            offset: this.offset,
            limit: this.limit
          }
        })
        .then(res => {
          this.records = res.data.recordList;
        });
    },

#コピペ後
    addFavoriteList() {
      axios.post("/addFavorite", {
          params: {  //※ここが間違い
              hoge: 'hoge',
              fuga: 'fuga',
          }
     })
      .then(res => {
          this.message = res.data
        });
    },

#コントローラー
class FavoriteListController extends Controller
{
    public function addFavorite(Request $request)
    {
        $id = Auth::id();
        $hoge = $request->input('hoge');
        $fuga = $request->input('fuga');

    //なにかしらの処理
    ....
}

#web.php
Route::group(['middleware' => 'auth'], function () {
    Route::get('/addFavorite', 'FavoriteListController@addFavorite');
   ...
}

getメソッドをコピッたのでparamsを消し忘れており、500エラーが発生。問題はここから。


firefoxデベロッパーツールで確認しており、ネットワークから500の部分をダブルクリックして、別タブでエラーの内容を確認しようとしました。
f:id:hiranami:20200223195824j:plain

すると、419 Page Expired が。
f:id:hiranami:20200223200224j:plain

ここで、419エラーと勘違いし、CSRF token関連のエラー記事を参考に試行錯誤を繰り返してしまいました。
※ちなみに、元のネットワーク画面の応答にサーバーのエラー内容ががっつりのってますw

色々と試してるうちに、他のpostメソッドでもネットワークをダブルクリックすると419ページに飛ぶことから、別タブで開いたことがCSRF token認証に失敗の原因であることに気が付きました。

ちなみに、上記操作をchromeで行うと500エラーの内容が表示されました。
f:id:hiranami:20200223200231j:plain


firefoxではネットワークから別タブで開いた場合 token情報を持っていませんでしたが、chromeはtoken情報を持っていたため419が発生せず。
ブラウザ間でこういう違いもあるんですね。 ( ̄- ̄)フーン ナルホド

終わりに

ちゃんとコンソール見ましょう。あと疲れたら早く寝ましょう。

【NGS】velvet・SPADesを使ったアセンブル

OS Ubuntu 18.04.2
conda 4.7.11

velvet インストール

# Anacondaでインストール
conda install -c bioconda velvet

アセンブル (k-mer 21)

k-mer は奇数しか指定できないので注意。

#処理するデータの準備。(fastaでもok)
velveth vel_21  21 -shortPaired -fastq **.fastq

#コンティグ作成
velvetg vel_21 -ins_length 300 -read_trkg yes -amos_file yes

#結果ファイル
less vel_21/Log

SPADes インストール

# Anacondaでインストール
conda install -c bioconda spades

アセンブル (kmer 21 ~ 55)

spades.py -k 21,33,55,77 --careful --only-assembler **.fastq -o spades_output

cab.spbu.ru

 isucon手順まとめ

isucon9に出場し予選落ちしました。
isucon7の問題を多少練習したので(isucon8もちょっと)、チーム内のwikiが消えても大丈夫なようその時の手順をメモします。
自分用なので雑。

初期設定

alp 導入

#ダウンロード
wget https://github.com/tkuchiki/alp/releases/download/v0.3.1/alp_linux_amd64.zip
unzip alp_linux_amd64.zip
#インストール
sudo install ./alp /usr/local/bin
#nginxの設定ファイルを修正
sudo vim /etc/nginx/nginx.conf
#設定ファイルのhttp {の部分に以下の内容を追加します。
http {
  log_format ltsv "time:$time_local"
    "\thost:$remote_addr"
    "\tforwardedfor:$http_x_forwarded_for"
    "\treq:$request"
    "\tmethod:$request_method"
    "\turi:$request_uri"
    "\tstatus:$status"
    "\tsize:$body_bytes_sent"
    "\treferer:$http_referer"
    "\tua:$http_user_agent"
    "\treqtime:$request_time"
    "\truntime:$upstream_http_x_runtime"
    "\tapptime:$upstream_response_time"
    "\tcache:$upstream_http_x_cache"
    "\tvhost:$host";
  access_log  /var/log/nginx/access.log ltsv;
}
#元のアクセスログを削除し、リロード
sudo rm /var/log/nginx/access.log && sudo systemctl reload nginx
#ベンチ実行
/home/isucon/torb/bench/bin/bench -data=/home/isucon/torb/bench/data -remotes=localhost -output=result.json
#alp確認
alp -f /var/log/nginx/access.log
alp --sum -f /var/log/nginx/access.log

netdata インストール (使わんかったけど一応)

#ワンライナーで起動までしてくれる。
bash <(curl -Ss https://my-netdata.io/kickstart.sh) all --dont-wait
#firewall ポート穴あけ
firewall-cmd --zone=public --add-port=19999/tcp --permanent
systemctl reload firewalld.service
#ブラウザで確認
http://127.0.0.1:19999/
#停止
systemctl stop netdata
#起動
systemctl start netdata

slow-log出力

MYSQL系の設定ファイルである/etc/my.cnfに以下のように設定を追加する

[mysqld]
# 以下の3行を追加する
slow_query_log
slow_query_log_file = mysql-slow.log
long_query_time = 0.5
このように設定すると、/var/lib/mysql配下にmysql-slow.logが出力されるようになるはず

サービスの再起動を忘れずにね
systemctl restart mariadb

pt-query-digestのインストール

以下のコマンドでインストールできる!

#cent0s
yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
yum install percona-toolkit

#ubuntu
wget https://www.percona.com/downloads/percona-toolkit/3.0.10/binary/debian/xenial/x86_64/percona-toolkit_3.0.10-1.xenial_amd64.deb
sudo apt-get install gdebi
sudo gdebi percona-toolkit_3.0.10-1.xenial_amd64.deb
あとは上記で出力したスロークエリログを解析する

pt-query-digest /var/lib/mysql/mysql-slow.log

mysql ダンプ

サーバ内の初期データをdumpにする
以下のコマンドでisubataテーブルだけを対象にdumpを作成する

mysqldump --single-transaction -u isucon -p isubata > /tmp/isubata_init.dump
出力したdumpを適当にWinSCPとかでローカルに持ってくる
ローカルにdumpを入れる(Windows

事前作業としてテーブルを作成しておく 配置先はC:\tmp配下

mysql isubata -u root --default-character-set=utf8 -p < C:\tmp\isubata_init.dump
※ローカルの初期rootパスワードはmypasswordだけど、たぶん自分で設定していると思うから自分のやつ打ってね

サービスをPythonに変更する

# サービスを停止する
systemctl stop torb.perl.service
# サーバ起動時に起動するサービスから除外する
systemctl disable torb.perl.service

# サービスを開始する
# sudo systemctl start torb.go.service
sudo systemctl start torb.python.service

systemctl enable torb.python.service

DB回り改善

slow.log確認
pt-query-digest /var/lib/mysql/mysql-slow.log
インデックス確認、作成
#テーブル名、カラム名などは適宜置き換えてください。
mysql>explain SELECT COUNT(*) as cnt FROM message WHERE channel_id = 10
#type が ALL/indexだと遅い。

#インデックス追加 ※カラムの順番重要
mysql>ALTER TABLE message ADD INDEX index_name(channel_id, id);

#インデックス削除※間違った時用
mysql>ALTER TABLE message DROP INDEX index_name;
https://qiita.com/katsukii/items/3409e3c3c96580d37c2b
http://tech.aainc.co.jp/archives/4634
外部mySQLへの接続
#mysqlのデフォルトポート3306の通信を許可する
sudo ufw allow 3306
※firewallが無効になっていたら無視。
mysqld.cnfの編集  
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
#bind-addressをコメントアウトする。
bind-address = 127.0.0.1

#rootユーザはどのIPからもアクセス可能に
mysql> grant all privileges on [データベース名].* to root@"%" identified by 'パスワード' with grant option
#外部mySQLへの接続が確認できたら、app側のsqlへの接続設定も変更する。

nginx周り改善

#ヘッダーへCache-Control追加  
#/etc/nginx/sites-available/default 
server{
	add_header Cache-Control public;
}
https://qiita.com/syou007/items/3e2d410bbe65a364b603

#ロードバランサ振り分け処理  
#/etc/nginx/nginx.conf 追記
http {
    upstream degumainux.com {
         #ip_hash;
        #least_conn;
        server [ip]:[port];
        server [ip]:[port];
    }

    server {
        location / {
            proxy_set_header Host $http_host;
            proxy_pass http://degumainux.com;
        }
    }
}
カーネル回り
#somaxconn数変更  
#確認
sysctl net.core.somaxconn

#somaxconn書き換え(一時的)
sysctl -w net.core.somaxconn=1024

#somaxconn書き換え(永続化)
#/etc/sysctl.confに追加
net.core.somaxconn = 1024

#リロード
sysctl -p
http://tetsuyai.hatenablog.com/entry/20111220/1324466655

dbrp、uniprotのapiをつかってpythonでデータ取得

自分用メモ ちょっと修正

dbrp_data_get.py

import json
import os.path
import sys

import requests


# 引数に指定したゲノム情報のアクセッション番号を検索し、accessibilityAPIの値からfastaファイルを取得するスクリプト。
# python dbrp_get.py [input_list]
def main():
    args = sys.argv
    file_name = args[1]
    print(file_name)
    url = 'https://www.nite.go.jp/nbrc/dbrp/api/dataget'
    data_format = '.zip'
    params = {'data_id': file_name}
    response = requests.get(url, params=params)
    dict = response.json()
    data_body_str = dict['result']['data_body']
    data_body = json.loads(data_body_str)
    accessibility_api = data_body['@graph'][0]['DataDownload']['accessibilityAPI']

    url2 = 'https://www.nite.go.jp/nbrc/dbrp/api/fileget'
    params2 = {'file_id': accessibility_api}
    download_data = requests.get(url2, params=params2)

    if not os.path.exists('./data/dbrp/'):
        os.makedirs('./data/dbrp/')

    with open('./data/dbrp/' + file_name + data_format, 'wb') as f:
        f.write(download_data.content)

    print("finish!")


if __name__ == '__main__':
    main()

uniprot_data_get.py

import requests
import os.path
import sys

def main():
    # 引数に指定したアクセッション番号のリストを指定し、uniprotのAPIでデータを取得するスクリプト。
    # python uniprot_data_get.py [input_list]
    args = sys.argv
    list_name = args[1]
    base_url = 'https://www.uniprot.org/uniprot/'
    format = '.fasta'
    list = open(list_name, 'r')
    if not os.path.exists('./data/uniprot/'):
        os.makedirs('./data/uniprot/')
    lines = list.readlines()
    for line in lines:
        print(line)
        accession_id = line.rstrip('\n')
        url = base_url + accession_id + format
        response = requests.get(url)
        with open('./data/uniprot/' + accession_id + format, 'wb') as f:
            f.write(response.content)

    print("finish!")

if __name__ == '__main__':
    main()