buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

CodeDeployでEC2にデプロイしようとした話

先ほどの記事に続きCodePipelineで自動デプロイできるよう直していきます!!

Buildも失敗

やっとSourceが成功したので次はBuild!って失敗かーーーい!

f:id:butorisa:20211219232148p:plain

Error calling startBuild: Bucket ********** does not exist 

ビルド用のバケットがないようだ Source同様作り直してBuild実行!成功しました

f:id:butorisa:20211219232707p:plain

Deploy

デプロイ先のEC2も作り直してまずDeployを実行してみて失敗原因を確認

f:id:butorisa:20211219235518p:plain

最初のタスクで失敗しちゃってますね。。「code deploy stop application 失敗」で検索したら解決策が出てきました!

zenn.dev

あ。そもそもEC2起動しただけでCodeDeployAgentインストールしてなかった笑

dev.classmethod.jp

nodejsとvue.jsもインストールしていなかったのでインストールしておく

qiita.com

それでもエラーが変わらないので手動でデプロイを作成してみた

qiita.com

んーそれでも上手くいかない。。明日またやろう

CodePipelineでS3にソースをダウンロード

去年CodePipelineでvue.jsプロジェクトを自動デプロイしようとしたが最後のデプロイで失敗して放置していたので直してみる!

qiita.com

Source失敗

1年以上寝かせておいたパイプラインを実行すると、なんと最初のGitHubからソースをダウンロードする段階で失敗!残念すぎる!!

エラー詳細を確認すると「S3 bucket was not found」っぽい文言でした

あれ?パイプラインでSource実行時に自動的にバケットって作成されなかったっけ??と思いつつ

S3を確認するとバケット1つもない!去年の自分がバケット消しちゃったんですね。。

Sourceバケット作成

ということでGitHubからダウンロードしたソースを配置するS3バケットを作成する

作成するバケット名はパイプラインの設定-アーティファクトストアで確認

f:id:butorisa:20211219231733p:plain

(とりあえずバケット作っておけばソース配置されるでしょって思って適当な名前にしたらダメだった)

それでは、パイプライン再実行!はい、失敗!

f:id:butorisa:20211219230300p:plain

[GitHub] Upload to S3 failed with the following error: The bucket does not allow ACLs 

アクセスコントロールリストが許可されてないと…

ACL許可のS3バケットを再作成

バケットACL設定はどこで編集するのか分からなかったので(できない?)バケットを再作成

バケット作成画面のここですね デフォルトはACL無効になっていました

f:id:butorisa:20211219230707p:plain

ついでにその下のパブリックアクセス設定も全て許可にしておきました

では再び、Source実行!今度は成功しました!!

f:id:butorisa:20211219231359p:plain

これでやっとスタートライン…

オンプレサーバ間のファイル共有方法

仕事でバックオフィスシステムでアップロードしたファイルがフロントサイトで

どのように参照されるかが知りたいがフロントサイトのコードを追うと該当箇所が多く

時間が足りないのでバックオフィスのアップロード方法だけ確認して、この方法で

アップロードしているからフロントサイトでの参照方法は…と推測できるようにする!

バックオフィス、フロントサイトは別々のオンプレミスサーバです

NFSディレクトリをマウント

baremetal.jp

バックオフィスサーバのNFSディレクトリにファイルをアップロードしているパターン

ファイルアップロード後にファイルにフロントサイトが参照するURL(フロントサイトのドメイン)を発行し

DBにファイル種別(広告バナーなど)とURLを保存していた

フロントサーバでもNFSがマウントされていてDBからファイル種別でURLを取得しHTMLに埋め込めんで表示している

rsyncで転送

アップロードされたファイルをバックオフィスサーバ内に配置しておきrsyncで他のサーバ(多分フロント)に転送しているパターンもあった

転送先はフロントサーバ自身だったら ./img/banner.jpg のように参照している

ただフロントサイトが転送されたファイル名を分からないといけないため、以下のようなルールがあるはず

  • 転送先のディレクトリは「トップページの広告エリアに表示するバナー画像」のように決まったファイル専用となる

  • 転送する1ディレクトリの中に複数のファイルがあり、ファイルの表示を制御したい場合は転送前にファイル名をキー値にする

    • 例えばキャンペーンのhtmlファイルを転送する場合はDBに登録されているキャンペーンID.htmlにする

    • フロントサイトではキャンペーン一覧表示時にDBからキャンペーンデータを取得し、IDから各キャンペーンhtmlを表示するリンクを生成する

結論

DBにファイルアクセス用URLが登録されていたらURL参照、登録されていなかったらフロントアプリでディレクトリ決め打ちしてファイルを取得している!!…はず

GitHub マージ方法の違いを調べた

これまで仕事でマージ方法を指定されたことがないのでマージコミットを作成する方法でマージしていたけど

そろそろ他の種類も理解しておいた方が良さそうなのでGitHubで提供されている3つのマージ方法を試してみた!

マージの準備として以下を行いました

  1. masterからfeature/#1ブランチ作成してカレントブランチにする

  2. コメント行を追加してfeature/#1にコミット・プッシュ

    • f:id:butorisa:20211218141708p:plain
  3. masterからfeature/#2ブランチ作成してカレントブランチにする

  4. 同じ箇所にコメント行を追加してfeature/#2にコミット・プッシュ

    • f:id:butorisa:20211218141753p:plain
  5. Create a merge commit でfeature/#1をmasterにマージ&プッシュ

    • f:id:butorisa:20211218143458p:plain

Create a merge commit

Create a merge commit でfeature/#2をmasterにマージしようとすると先ほどマージした箇所と競合します

f:id:butorisa:20211218143815p:plain

 Create a merge commit を選択すると手動マージしてくださいとのこと(競合箇所が多いと焦るあるあるですね)

f:id:butorisa:20211218144130p:plain

Atomで競合箇所を確認してfeature/#2の修正を選択しマージ・プッシュ!

f:id:butorisa:20211218150640p:plain

マージ先のmasterは新たに作成されたマージコミットの状態になっている(non fast-forwardマージ

f:id:butorisa:20211218151747p:plain

featureブランチを削除してもマージ履歴は枝分かれしています(このマージだったらfeature削除しないほうが履歴が追いやすくて良さそう)

f:id:butorisa:20211218162009p:plain

Squash and merge

再び上記の準備を実施した状態(今度は#3、4ブランチ)

f:id:butorisa:20211218152944p:plain

Squash and merge を選択し先ほど同様に手動マージを行いfeature/#4をmasterにマージ&プッシュ

このマージ方法だと#4ブランチは枝分かれしたままで#3ブランチに#4の修正点が付け足されています(こちらもnon fast-forwardマージになっている)

f:id:butorisa:20211218153113p:plain

masterにマージした後featureブランチを削除すると修正が1本線でされたようになります(feature削除する場合はこちらが良さそう)

f:id:butorisa:20211218161454p:plain

Rebase and merge

最後にrebase編!準備を実施(今度は#7、8ブランチ)masterは#7ブランチの状態

f:id:butorisa:20211218154347p:plain

※#5、6でやってみたが動きがつかめなかったのでリトライ!

Rebase and merge を選択するとまずrebase実行のアラートが出て手動マージ

これまでのマージ方法ではour changesがマージしようとしているブランチの修正だったけどrebaseマージでは逆になっている

(#7ブランチをrebaseするからこちらの修正がour changesになるってことか!rebase解説はこちら

f:id:butorisa:20211218154915p:plain

feature/#8ブランチの修正を選択してマージ&プッシュ!masterはpull1件、push1件になっています

f:id:butorisa:20211218155210p:plain

f:id:butorisa:20211218155300p:plain

fork GUIでpullだけ実行しようとしたらまた競合が発生したのでgithub GUIでforce pushするとリモートmasterが#8の状態になった

f:id:butorisa:20211218160508p:plain

featureを削除すると#7での修正履歴は消え、単純にmaster修正前→#8に修正されたように見える

f:id:butorisa:20211218162719p:plain

f:id:butorisa:20211218162807p:plain

rebaseマージの使いどころが分かっていないがfeatureブランチの修正範囲が大きくコミット回数が多いが、コミット履歴は不要という場合に使えるのかな??

Step FunctionsでLambdaを連続実行してみる

AWS StepFunctionsがどんなサービスか分かったので使ってみる!

StepFunctionsで実現することはこちら

  1. おくすり登録lambda関数(引数に薬の名前・服用回数を受け取り、薬の名前と服用タイミングを返す)

  2. リマインダーlambda関数(引数に開始時間・イベント名を受け取り、SES送信)

これだけだったら普通にlambdaの連続実行でいいんですけどね^^;

なんちゃってマイクロサービスでリマインダー機能は独立させておいて今後の(あるか分からない)機能拡張により

レッスン予定登録、資格勉強タスク登録…など他の機能からも汎用的に実行できる構成にしたということで!!

Lambda関数を作成

おくすり登録関数

引数はこんな感じ

{
  "name": "ステロイド1/6錠",
  "timesDaily": "2"
}
exports.handler = async(event) => {

    let start;

    if (event.timesDaily == 1) {
        start = "15:00";
    }
    else if (event.timesDaily == 2) {
        start = "10:00, 17:00"
    }
    else if (event.timesDaily == 3) {
        start = "10:00, 15:00, 20:00"
    } else {
        return
    }

    const response = {
        statusCode: 200,
        start: start,
        title: "[おくすり]" + event.name
    };
    return response;
};

リマインダー関数

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({ region: 'ap-northeast-1' });

// Create sendEmail params 
var params = {
    Destination: { /* required */
        CcAddresses: [],
        ToAddresses: [
            'メールアドレス',
        ]
    },
    Message: { /* required */
        Body: { /* required */
            Html: {
                Charset: "UTF-8",
                Data: "HTML_FORMAT_BODY"
            },
            Text: {
                Charset: "UTF-8",
                Data: "TEXT_FORMAT_BODY"
            }
        },
        Subject: {
            Charset: 'UTF-8',
            Data: 'my reminder'
        }
    },
    Source: 'メールアドレス',
    /* required */
    ReplyToAddresses: [],
};

// Handle promise's fulfilled/rejected states
exports.handler = function(event, context, callback) {
    params.Message.Subject.Data = "[reminder]" + event.start + event.title;
    params.Message.Body.Html.Data = "本日のスケジュール<br>" + event.start + " : " + event.title;
    params.Message.Body.Text.Data = "本日のスケジュール¥n" + event.start + " : " + event.title;
    return new AWS.SES({ apiVersion: '2010-12-01' }).sendEmail(params).promise();
}

関数をStepFunctionsで実行

それでは作成した2つの関数をStepFunctionsから連続実行してみます!

ステートマシン(StepFunctionsで実行する処理をまとめたもの)を作成して実行すればOK

マネジメントコンソールからステートマシン作成をすると作成方法が選択できる

これまではjsonを書いて作成していたみたいだけど、このようにフロー図にドラッグ&ドロップで簡単配置★!

f:id:butorisa:20211218111404p:plain

ステートマシンが作成できたのであとは実行するだけ って失敗してる!

f:id:butorisa:20211218111855p:plain

because no identity-based policy allows the lambda:InvokeFunction action

いつも通り実行ロールがないエラーですね。(AWSで何か試そうとすると遭遇率100%なんだよな。。)

AWSLambdaRoleをアタッチして再度実行!今度は正常終了しました

f:id:butorisa:20211218113130p:plain

リマインドのメールも届きました!

f:id:butorisa:20211218113447p:plain

not authorized to perform: ses:SendEmailは寝かせて直る!

3日前にAWS Lambdaでメール送信(SES)をしてみるため以下ステップを実行

  1. メール宛先アドレスをSES認証

  2. 公式ガイドをコピペしてSES送信関数を作成

  3. テスト実行

こちらのエラーが発生してメールは送信されませんでした。。

not authorized to perform: ses:SendEmail

Lambda関数にSES送信ロールがないということなのでIAMロールでSES送信を許可してみたが、上記エラーが発生する

今度はIAMロールをjson編集でSES送信を許可したが、変わらず…

画面を更新してリトライ、ロール編集してリトライ…と1時間近く頑張ってみたがダメだった

さっきロール見直ししようかな〜と試しに関数を実行すると、成功してメールが送信された!え、なんで

IAMロールの適用に1時間以上掛かることがある!?とは思えないから、関数実行の画面を開き直す必要があったのかも

ちなみにSES送信できたIAMロールはこちら

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "logs:CreateLogGroup"
            ],
            "Condition": {
                "StringLike": {
                    "ses:FromAddress": "メールアドレス"
                }
            },
            "Resource": [
                "arn:aws:ses:ap-northeast-1:xxxxxxx:identity/メールアドレス",
                "arn:aws:logs:ap-northeast-1: xxxxxxx:*"
            ]
        }
    ]
}

稼働中RDS同士のデータ同期を考える

仕事で既に稼働しているEC2+RDSのセットが2つあってそれぞれのRDSのデータを同期するかも、という話があったので勉強がてら調べてみる

AWS DMS

一般的な方法のように思われる

AWS DMS を活用して MySQLからPostgreSQLへの移行とDBリファクタリングをやっている話/osc2018-kagawa-dms-replication

DMSを使ったSQL Serverの継続的なレプリケーションを行う

データ移行なので基本は1度または何度かの移行タイミングで利用するサービスに思えたけど、

データの更新があったらもう一方のDBへ反映させる継続的レプリケーションという機能があった!

Kafka

打ち合わせの中で出てきた案(そもそも知らない)

データストリーム、データハブ(正確にはメッセージキューというらしい)

オンプレではApacheが提供しているKafkaが使われていてAWSマネージドサービスでも提供されるようになった(Apacheのものより設定が楽らしい)

こちらの記事の図が分かりやすかったです!!

Apache Kafkaの概要とアーキテクチャ

何も考えずにEC2上のアプリ内でデータ抽出+別アプリのDB更新(API実行)をするとデータが大量だった時にEC2の負荷が高くてレスポンス遅くなっちゃう…

公開APIを使ったデータ更新では1度に送信できるリクエストに制限があり、Max件数をリクエストすると非同期で更新処理が実行されるので更新結果取得をポーリング実行してデータが正常に更新できたかを確認する方法を使ったことがあります

ちなみにMax件数をAPIリクエストして更新次第、次のMax件数をリクエスト…を何度か続けて実行したらサーバエラーになって一時的にAPIリクエストが拒否されてしまったこともTT

こんな問題を解消するのがデータハブなんですね!

Kinesis

とてもうっすらとした記憶でキネシスストリームっていうのも同じようなサービスじゃなかったっけ?と浮かんできました

Amazon Kinesis vs. Kafka: データストリームサービス徹底比較

KafkaとAWS Kinesisの比較

Kafkaの方がKinesisと比べるとより大量のデータを瞬時に扱える、でも熟練者じゃないとセットアップが難しいっていう感じなんですね〜(Kinesisがいいじゃん笑)

まとめ

Kafka、Kinesisだったらデータ自体は高速で繋いでくれるけど、DB更新するにはAWS Lambdaでデータが送られてきたことを検知してRDSに登録する必要がありそう

高頻度かつ大量のデータの同期であればKafkaまたはKinesis+AWS Lambdaなんだろうけど、AWS DMSの継続レプリケーションで十分なんじゃないかな