容量不足は突然に…

リモートの開発環境にプロジェクトで必要になった新たなコンテナを試しに起動してdocker composeに記載することをしていたら、突然保存ができなくなってしまいました。
dockerでポコポココンテナ建ててた以外になにもしていなかったので、正直困惑していました。

1. 犯人探し

まずは、ディスクの空き容量を確認してみましょう。ディスク容量の確認は以下のコマンドを打ち込むと簡単に確認できます。
ちなみに、-hオプションは、人間に読みやすいように表示してくれます。

df -h

ディスク容量が100%になってしまっています!これで保存できない原因がなにかの不具合ではなく、本当に容量が足りないということが判明しました。


次に、どこのディレクトリが容量を食っているのか確認していきましょう。
duコマンドでディレクトリごとにどれくらい容量があるのか表示してくれます。
手始めに、一番上の階層から確認するのが妥当でしょう!

sudo du -shc /*

/var が58GBくらい使っていました…..

さらにduコマンドでvar配下を見てみましょう

sudo du -shc  /var/* | sort -rn

/var/libが58GBの大半を占めているようです。

さらに、duコマンドで奥地に足を踏み入れてみましょう。

sudo du -shc  /var/lib/* | sort -rn 

/var/lib/dockerがとてつもなく肥大化している模様です…
いろいろなイメージやコンテナキャッシュがあるのでもしかしたらと思っていましたが、ここまで肥大化するとは思っても居ませんでした。

しかし、何に対処すればいいのか?が明確になったので、ここからはDocker との戦いを開始します。


2. Dockerの不要なデータを削除

不要なイメージを削除しましょう。使っていない不要なイメージがあるはずです。

docker image prune -f

と、思ったのですが表示された内容は「Total reclaimed space: 0B」と何も消すものが無いという状態….

こうなったら、奥の手を使うほかありません。ボリューム・イメージ・コンテナで不要なものを全て消し去るコマンドを実行します。

docker system prune -a

最後に容量が増えているか確認してみましょう…

df -h

これで、スッキリ年末の大掃除完了です。

SQSのvisibleメッセージの数だけECSタスクを起動する方法

ご無沙汰しております。オンラインコンサルタントの直井です。今回はECS周りについてご紹介いたします。

1. 動機

ECSのタスクをSQSのメッセージ数に応じてオートスケールしたいな…と思ったことがことの発端です。

要は、バッチ処理をメッセージに応じてスケーリングするといった内容です。今回の内容ではクラスター内のサービスだと、メッセージ数に応じてタスクをスケールアウトすることは可能ですが、スケールインの設定が難しいため、このような実装方法になりました。

2. 動作環境

Python:3.9

ECSのタスクは、処理が終了すればコンテナが終了してスケールインの操作が不要な状態

3. Lambdaサンプルコード

import boto3
import os
import time
import datetime

ecs_client = boto3.client("ecs")
cw_client = boto3.client('cloudwatch')

ECS_CLUSTER = ""
TASK_DEFINITION = ""
SUBNET_ID_1 = ""
SUBNET_ID_2 = ""
QUEUE_NAME = ""
def lambda_handler(event, context):
    
    response = cw_client.get_metric_statistics(
               Namespace = 'AWS/SQS',
               MetricName = 'ApproximateNumberOfMessagesVisible',
               Dimensions = [
                            {
                                'Name': 'QueueName',
                                'Value': QUEUE_NAME
                            },
                        ],
                StartTime=datetime.datetime.now() - datetime.timedelta(seconds=60),
                EndTime=datetime.datetime.now(),
               Period = 60,
               Statistics = ['Maximum']
               )
    metric = int(response['Datapoints'][0]['Maximum'])
    if metric > 0 :
        print("Create!!")
        for num in range(metric):
            ecs_client.run_task(
                cluster=ECS_CLUSTER,
                launchType="FARGATE",
                networkConfiguration={
                    "awsvpcConfiguration": {
                        "subnets": [SUBNET_ID_1,SUBNET_ID_2],
                        "assignPublicIp": "ENABLED",
                    }
                },
                taskDefinition=TASK_DEFINITION,
            )
            time.sleep(10)
    else:
        print("None")

ざっくりとコード内の説明

発火した過去1分でSQSで利用できるメッセージ数(visibleCount)を取得して、その数に応じて繰り返し処理でECSのタスクを起動させるようにしています。

visibleカウントは処理中ではないメッセージ数に応じて値が変化します。

4. 発火イベントを仕込む

右上のトリガーの追加を選択

バーに「event」と入力して「EventBridgeを選択」

「新規ルールの作成」を選択肢、「ルール名」と「スケジュール式」を入力して「追加」ボタンをクリック

※1分や1時間の場合は、1 minute, 1 hour だが、2以上であれば 2minutes, 2 hoursになるので注意

5. 最後に

サービスをうまく使ってどうにかできないか考えましたが、今回構成したシステム群が疎結合に作れたので、柔軟性があり助かりました。

ECSではちょいちょいコンソール画面に対応していないもしくは、新UIに対応していないことが多いので戸惑うことが多かったです。

SQSの メッセージ最大サイズ「256KB」にキレてます(>_<)

お久しぶりです。オンラインコンサルタントの直井です(^^♪
皆様いかがお過ごしでしょうか?最近は暑さも和らぎ、比較的過ごしやすい日々が続いております。気温の変化は体調にも表れやすいので、どうぞお気をつけてお過ごしください。

という堅苦しいはいりは置いておいて、今回はタイトルの通りSQSの最大メッセージサイズが256KBということにキレています。このサイズ、微妙に足りなくないですか…?そういうのには向いてないとか、そもそもそんな大きい内容をキューに入れておくな!とか言われるかもしれませんが、入れたいんですわ!入れないと進めないんですわ!なんて状況は多々あると思います。

1. SQSを利用する構成

そもそも、どんな感じでSQSを利用するか?について説明いたします。今回弊社ではルート計算プログラムをサーバレス環境に移行すべく、API Gateway,Lambda, SQSを利用を検討しました。以下の図の通りです。

これまで動いていた環境では、高性能なサーバで常にリクエストを待ち受けて、リクエストがあったら計算する形だったので、待ち受け時間が多いと不要なリソースとして問題がありました。これを解決するため、イベント駆動型に近いサーバレス環境に移行する計画が実行されました。

2. リクエストが肥大化していた

プログラムで計算するための情報は、計算して欲しい地点の位置情報(緯度経度)情報くらいなのでPOSTデータはそこまで大きくないだろう(^^♪ラッキー!標準機能でごり押し移行ができると考えていました。

ですが、実際にリクエストされる内容を確認してみると….その地点の位置情報だけでなく、備考情報やid等計算に必要ない内容もPOSTされており、戦慄しました。また、その内容をそっくりそのまま返す必要があります。

このため、リクエストが、2MBや5MBなどを超えることが多々あります。このことから、SQS標準のメッセージ容量では足りないことが明らかとなりました。

3. メッセージを拡張

このような困った状況でも、AWSは最適なソリューションを用意してくれていました。S3を使えばメッセージの容量を最大2GBまで拡張できるよ!とのことです。これは心強い!2GBもあれば何でもできます。なんでも。というかS3を使った時点で何でもできるような気がしてきました。

https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-s3-messages.html

4. リクエストを受け付けてSQSに登録する

できそうなことがわかったので、とりあえず作っていきます。作る部分は、APIGatewayから受け取るLambda関数です。今回の計算プログラムがScalaで掛かれていたので、それそそっくりそのまま受け継ぎます。Lambdaのランタイムは、java on amazon linux2 を利用します。

受付部分はさっきの公式ドキュメントをそのまま利用します。

    import software.amazon.awssdk.regions.Region
    import software.amazon.awssdk.services.sqs.SqsClient
    import software.amazon.awssdk.services.sqs.model._
    import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
    import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
    import com.amazon.sqs.javamessaging.AmazonSQSExtendedClient
    import com.amazon.sqs.javamessaging.ExtendedClientConfiguration
    import software.amazon.awssdk.services.s3.S3Client
    import software.amazon.awssdk.services.s3.S3ClientBuilder

    def sentSqsMessage(request_json: String): SendMessageResponse = {

        val S3_BUCKET_NAME: String = "sqs_message_bucket_name"
        val AWS_ACCESS_KEY: String = ""
        val AWS_SECRET_KEY: String = ""
        val QUEUE_URL: String = "sqs_queue_url"

        val awsCredentials = StaticCredentialsProvider.create(AwsBasicCredentials.create(AWS_ACCESS_KEY, AWS_SECRET_KEY))
        val s3Client: S3Client = S3Client.builder().region(Region.AP_NORTHEAST_1).credentialsProvider(awsCredentials).httpClient(ApacheHttpClient.create()).build()
        val qsClient: SqsClient = SqsClient.builder().region(Region.AP_NORTHEAST_1).credentialsProvider(awsCredentials).httpClient(ApacheHttpClient.create()).build()
        val extendedClientConfig = new ExtendedClientConfiguration().withPayloadSupportEnabled(s3Client, S3_BUCKET_NAME)
        val sqsExtendClient: AmazonSQSExtendedClient = new AmazonSQSExtendedClient(sqsClient, extendedClientConfig)
        val sendMessageRequest: SendMessageRequest = SendMessageRequest.builder().queueUrl(QUEUE_URL).messageBody(request_json).build()
        val sendResult: SendMessageResponse = sqsExtendClient.sendMessage(sendMessageRequest)

        return sendResult
    }

ざっくりと処理の流れを記載すると、AWS クレデンシャルをを生成し、S3とSQSのクライアントを作成します。その後に、SQSをS3を利用して拡張するためのクライアントを作成しリクエストを投げる流れになります。

5. 最後に

あまり日本語で書かれている記事がなく、試し試しで動かしたことを昨日のことのように思い出します….

コミットメッセージにチケット番号がない場合に警告を出すGithook

オンラインコンサルタントの直井です(^^♪

前に記事を投稿してから早一日が経過しました。
よく、「ローマは一日にして成らず」と言われますが、GitHooksは違います。
「GitHooksは一日にして作成すべき」これは、ローマ帝国から受け継がれてきた名言です。
なぜ、一日でやらなくてはいけないのか?
それは、業務効率化のツールは、すぐに共有すべきであるからです。効率的なツールの作成は有用な時間を作ることができるのです。

そんな冗談はさておき、今回の記事は以下の3本建てでお送りします。
・「GitHooksを利用してコミットメッセージに、ブランチ名からチケット番号を自動的に追加する」
・「チェックアウトしたときに、ブランチ名をチェックする」
・「GitHooksを共有する方法」


そもそも、何を解決したいのか?

そもそも、今回の問題はなんでしょうか?

現状:チケットとGitを連携するには、コミットメッセージにチケット番号が必要だが、コミットメッセージにチケット番号を入れるのを結構忘れがち → チケットとの関連付けや終了が自動的にされない。

解決したいこと:コミットメッセージにチケット番号を入れたい

ざっとまとめるとこんな感じです。なので、今回の問題はいたってシンプルに「
コミットメッセージにチケット番号を入れたい 」という願いをかなえるだけです。


どうやって実現させるか

状況が整理され、問題が明確になったところで、今度は最も重要な部分….
どうやってこの問題を解決するかです。

毎朝出社時に「 コミットメッセージにチケット番号を! 」と力技オペレーション解決もアリです。しかし、これはスマートでない解決方法です。

今回天才的な発想で思いついた解決方法は以下の通りです。
①:ブランチ名の最初にチケット番号を入れる
②:ブランチ名から自動的にコミットメッセージにチケット番号を入力する
③:①、②のアクションをする際に、チケット番号らしき数字が見つからなかったら警告を出す。

修正や新規機能を開発するときにすること、それはブランチを切る!ということは?その行為自体はチケット番号と結びつきがあるのです。また、ブランチに番号が付くことで、たくさんあるリモートブランチから目的のブランチを探しやすくなります。


1. ブランチ作成時チケット番号が書いていなさそうな時にエラーを表示するGitHookを作成

GitHookでは、様々なGitアクションに対してフックを設定することができます。
ブランチを作成したというアクションに対するフックはないので、厳密ではないですが、チェックアウトしたときに発動する「post-checkout」を利用します。

#!/bin/sh

# PHPStorm勢からのアツい要望
git pull

ticketNumber=`git branch | grep "*" | awk '{print $2}' | sed -e "s/^\([0-9]*\).*/\1/g"`
if [ -n "$ticketNumber" ]; then
    exit 0
else
    # チケット番号がないブランチ
    echo -e "チケット番号がありません。"
    exit 1
 fi

PHPStorm勢からのアツい要望とありますが、チェックアウトしたときにGitPullされていないことが何回かあったというお話を聞いてついでに入れました。

それ以降は、ブランチ名からチケット番号らしき数字を取得して、それがあればOKなければエラーを表示するようにしました。

eixt のコード0が正常に終了したことを伝え、エラーの場合は0以外(ここでは1)を指定するようにします。


2. ブランチ名からチケット番号を取得して自動的にコミットメッセージに組み込むGitHookを作成

前のセクションでブランチ名にチケット番号があるという前提ができました。
これ以降は、ブランチ名にチケット番号が入っているので、それをコミットメッセージにチケット番号を入力すればいいだけです。

#!/bin/sh

if [ "$2" == "" ] ; then
    ticketNumber=`git branch | grep "*" | awk '{print $2}' | sed -e "s/^\([0-9]*\).*/\1/g"`
    if [ -n "$ticketNumber" ]; then
        mv $1 $1.tmp
        echo "fix #${ticketNumber}" > $1
        cat $1.tmp >> $1
    else
	# チケット番号がないブランチ
	echo -e "チケット番号がありません。"
	exit 1
    fi
fi

さっきのpost-checkoutを流用しただけのお手軽ファイルを作成します。
ブランチ名からチケット番号らしき数字を取得し、コミットメッセージの最初にfix #チケット番号 を追加しています。

※すべてのコミットにこれが適用されます。


3. 作成したGitHooksを共有する

基本的に、GitHookは「.git/hooks」で生成・管理されるので、Git管理下にありません。
ですが、今回はチームメンバーと共有しなくては意味がありません。

なんと便利なことにコマンド一つでGitHooksのパスを変更することが可能なのです!

git config core.hooksPath <githooks dir>

超簡単!一番最後の引数にgithookしてほしいファイルが入ったディレクトリを指定するだけです。今回は、プロジェクトルートに「.githooks」ディレクトリを作成し、その中に1と2で作成したファイルを入れます。

なので、この場合下記コマンドを実行するだけで、良いわけです。

git config core.hooksPath .githooks

3 – 1 ダブルクリックだけで済むようにする

このコマンドをターミナルに入ってプロジェクトのあるディレクトリで実行してくれ!というのも簡単ですが、さすがに不親切な気がするので、batファイルを作っておきます。(windowsユーザーがほとんどなので)

@echo off
cd %~dp0
git config core.hooksPath .githooks

cd %~dp0 でこのbatファイルが存在するディレクトリに移動
そして先ほどのコマンドを実行

ただそれだけのファイルですが、あると便利だしみんな助かるので作りましょう。

※作成したbatファイルと.githooksが同じ階層にある前提です。

3 – 2 一時無効化batファイルを作成

急な修正やチケットがない変更…その他もろもろの理由で今回作成したgithooksが鬱陶しく感じる時があるかもしれません。
ですので、事前に一時無効化できるようにしておきます。

chcp 65001
@echo off
cd %~dp0
git config core.hooksPath .git/
set /p x="GitHooksの機能を一時的に無効化しました。元に戻しますか?(y/n):"
if "%x%" == "y" (
    git config core.hooksPath .githooks
)

このファイルをダブルクリックすることで、一時的に無効化されます。
その後はキー入力次第で元に戻すかどうか選択することができます。

一番上に「chcp 65001」と入れておくことで、日本語の文字化けを防げます。

hooksPathを./gitにしておくことで、何も読み込まれない状況を作り出しています。これは、./git/hooks/にはあるが./gitではファイルが見つからないため、動作しません。ですので、新しく空の「empty_githooks」ディレクトリを作成し以下のようにすることも一つの策です。

git config core.hooksPath empty_githooks

最後に

これでひとまず、プログラム側でどうにかできたので、後は運用面で浸透させる必要があります。

もしかすると、ここが一番肝で難しいことかもしれませんね…

それでは、またお会いしましょう!(^^)!

GitHubとRedmineを連携させてチケット管理を楽にする方法

オンラインコンサルタントの直井です(^^♪

4月になり、寒暖差がとても激しいですが、いかがお過ごしでしょうか!(^^)!
冬用のアウターを完全に収納してしまったので、仕事の行はまだしも、帰りに凍えながら通勤しています(T_T)

さて、今回は表題の通りチケット管理をもっと楽に!というテーマでお話できればと思います。


チケット閉じ忘れ問題

「 問題が発生した!チケット作ろう! 」と考えチケットを追加してくれる方はかなりいらっしゃるのですが、それを終了にすることを忘れがちです….なので、「本当は終わっている」のに終了になっていないチケットが多数存在していました。

それによって、同じ意味のチケットが2つ以上存在してしまったり..なんてことも…

遠足でよく聞くフレーズのように、「チケットを閉じるまでが修正」これを開発メンバー全員に毎日チャットで送れば少しは改善するかもしれません。
しかし、こんな非効率的なことをしても互いにうっとおしいだけで、またチケットが閉じられなくなる日が目に見えています。


チケットとGitを連携させる

どうすれば、チケットの閉じ忘れが解消されるか?

修正は、いずれ origin/main ブランチにマージされます。
それは、チケットが終了したことを意味します。
ということは….
「 origin/mainにマージされた内容(コミットメッセージ等)にチケット番号があればそのチケットは終了 」にすればいいのでは?

そう考えた我々はアマゾンの奥地へ向かった…


1. 環境

1 Git(remote)
・GitHub
2 チケット管理
・Redmine( 4.1.1.stable )

今回は上記のような構成でお送りします。バージョンなどの違いで少し手順が変わっているかもしれません。

また、プラグインを導入する際に、うまくインストールできない場合があると思います。その際は、下記リンクの記事を参照ください。
https://oc-technote.com/%e6%a5%ad%e5%8b%99%e5%8a%b9%e7%8e%87up/redmine%e3%81%ab%e3%83%97%e3%83%a9%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%92%e5%85%a5%e3%82%8c%e3%82%8b%ef%bc%81/


2. 手順

2 – 1 redmine_github_hookについて

今回利用するプラグインは「 redmine_github_hook 」です。
こちらのプラグインがどのように動作するか、簡単に説明すると下記のような形です。

①:プラグインを発火させるためのURLを踏む
②:git fetchでリモートリポジトリとプラグイン内部にあるローカルリポジトリを同期
③:コミット内容を読み込んで、チケットのアクションを実行(終了や関連付け)

サクッと①を行うことで自動的に②、③が実行されます。詳しいライフサイクルはこちら

なんて便利なプラグインなのでしょうか!デフォルトで入っていてもいいような気がしますが…
しかし、今回実現したいことをやろうとするといまいちな箇所があります。

・②を実行すると、developや開発途中のリモートブランチなどの変更も読み取って終了になってしまう(git fetchですべてのブランチの変更をくみ取るため)


2 – 2 Redmineにプラグインを追加

redmineアプリディレクトリのpluginsディレクトリ配下にプラグインファイルを配置します。(bitnamiスタックの場合「~/stack/apps/redmine/htdocs/plugins」の可能性が高いです。)
また、今回使用するプラグインは「redmine_github_hook」ですが、ほかのプラグインも同様の手順でインストールすることができます。

今回、マスターにブランチにマージされた内容だけをくみ取りたいので、下記URLからプラグインを追加
https://github.com/asurann123/redmine_github_hook

git clone https://github.com/asurann123/redmine_github_hook.git

このリポジトリで変更している箇所は、「app/services/github_hook/updater.rb」の最下部にある関数を変更しています。環境によってはmainブランチだったりするかもしれないので、適宜変更をお願いします。(Gitがうまく吸収してくれる可能性あるかも?)

ここまで来たら、後はreadmeにある内容の通りです。

アプリケーションのトップディレクトリに戻っておく(bitnamiの場合)

cd ~/stack/apps/redmine/htdocs/

gem追加

gem "redmine_github_hook"

インストール

 bundle install --no-deployment

マイグレーション

 bundle exec rake redmine:plugins:migrate RAILS_ENV=production

再起動(bitnamiの場合)

sudo /opt/bitnami/ctlscript.sh restart

2 – 3 プラグイン用にリポジトリをclone

bareオプションを追加して適当な場所にリポジトリをクローン

 git clone --bare <Repos URL or SSH>

リモートを追加

git remote add origin <Repos URL or SSH>

2 – 4 プロジェクトにリポジトリを設定

プロジェクトの「設定」から「リポジトリ」を選択し、「新しいリポジトリ」をクリック

先ほどクローンしたpathを入力する。メインであればメインにチェックし、識別子があると後々わかりやすいが、記入しなくてもいい

2 – 5 発火URLの設定

ここまででとりあえずGitとRedmineの連携設定は完了です。しかし、動作させるには発火URLを踏む必要があります。

ほかのサイトを参考にすると、Github Actionsを利用したりしています。
今回は、デプロイの1セクションにこれを追加しようと思います。

簡単にPythonファイルを作成して、デプロイのお好きなタイミングにこのPythonファイルを実行するだけです。AWSをご利用の場合、CodePiplineなどを利用されていると思いますので、その間にLambdaで発火させるだけです。


import requests

response = requests.post('https://<your domain>/github_hook?project_id=<project_id>',params={"project_id":"<project_id>"})

3 . 最後に

かなり超大作になってしまいました💦しかし、同じような内容でお困りの方が少しでも参考になればと思います。

一年前に作業した内容なので、もしかすると情報が古い可能性があります。

次回は、「 コミットメッセージにチケット番号がない場合に警告を出すGithook 」編でお会いしましょう!