容量不足は突然に…

リモートの開発環境にプロジェクトで必要になった新たなコンテナを試しに起動して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

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

【Java】Gsonオブジェクトが、toJsonメソッドを呼び出した際に値がnullのものは、消失してしまう

タイトルだけだとどういうことかわからないので、サンプルを書きます。

目標はGsonを使って以下のようなJsonを生成することです。

{"hoge":"hoge","fuga":null}

Gsonを使ってJsonを生成するのは慣れていた(つもり)だったので以下のようにサラサラと書いてみます。

Map<String, String> map = new HashMap<>();
map.put("hoge", "hoge");
map.put("fuga", null);

Gson gson = new Gson();
String json = gson.toJson(map);

jsonの中身を見ると…

{"hoge":"hoge"}

俺のfugaが消えています…

調べてみると、GsonにはBuilderが用意されてて、そこでserializeNulls()を呼んで create()すると良いみたいです。

Map<String, String> map = new HashMap<>();
map.put("hoge", "hoge");
map.put("fuga", null);
GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls();
Gson gson = gsonBuilder.create();
String json = gson.toJson(map);

結果

{"hoge":"hoge","fuga":null}

fugaを取り戻しました。

serializeNulls()の中身を見ると、GsonBuilderのserializeNullsプロパティがtrueにするような処理が走っていました。
serializeNullsプロパティはデフォルトではfalseになっていました。

そもそも GsonBuilder なるものがあるのを知らなかったですね…勉強になりました。

<参考サイト>

【Quita 】Gsonはデフォルトではnullなフィールドをシリアライズしない

【Java】JSONObjectで、getIntやoptIntで null の場合は0にせず null のままでいてほしい

int 型 はnullにならないので、しょうがないのですが、optIntegerのようなものが欲しいわけです。
本件、タイトルのことを上司に聞いたら即、一番下にある参考サイトを見つけてくれました。
情報収集力に雲泥の差がありますね…。

JSONObjectにそんなものがなかったのでなければ、作りましょうというのが今回のお話です。
ついでにDouble型も作ります。

public class JSONUtil {

    public static Integer optInteger(JSONObject jsonObject, String key) throws JSONException {
        return (jsonObject.isNull(key)) ? null : jsonObject.getInt(key);
    }

    public static Double optWrapperDouble(JSONObject jsonObject, String key) throws JSONException {
        return (jsonObject.isNull(key) || Double.isNaN(jsonObject.getDouble(key))) ? null : jsonObject.getDouble(key);
    }

}

0 とか 入力なしをきちんとわけたいときってどうするのがいいのかいまだに迷います。
Stringは、””でいい気はしますが…。
個人的には、nullは、 すぐNullPointerExceptionにはまるため使いたくないので…。

<参考サイト>

【Stack Overflow】Json Parsing and Nullable int value in android

【Java】Caller パッケージ名 needs to hold android.permission.SCHEDULE_EXACT_ALARM to set exact alarms.

タイトルのエラーに出くわしてしまいました。

AndroidManifest.xml内 で以下のように宣言するだけで良いと思っていたので出くわす理由がわかりませんでした…

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

公式ドキュメント

AlarmManager を使って正確な時間にアラームを設定してサービスを動かしたかったのでこれで、AlarmManager の setExact() などのexactを含むメソッドを利用できると思っていました。

てか、実際にそう出来る端末がありました。
ただ、他にandroid でタイトルのエラーを起こす機種がいました。

何はともあれ対策です。

どうやら AlarmManager には 正確にスケジューリング出来るか確認できるメソッド canScheduleExactAlarms() があるようです。

android12以上だと使えます。

AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
    alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
} else {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
}

canScheduleExactAlarms() メソッドのドキュメントを見ると

SCHEDULE_EXACT_ALARM権限を持つ、またはデバイスの省電力除外リストに入っているとtrueを返してくれるみたいです。

つまりエラーが起きたということはどちらともダメだったということです。
上述した実際に出来た端末は元々デバイスの省電力除外リストに入れていたからっぽいですね…

なんにせよユーザー側で設定を変えれるので上記のような分岐は行う必要があります。

Exactを使わせたい場合は、Intentで「Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM 」を指定して設定してもらうか省電力モードから外す必要がありそうです。

12への変更点に書いておいて欲しかったです…

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に対応していないことが多いので戸惑うことが多かったです。