Kubernetes を利用時、大きいデータサイズの画像を複数回POSTするとエラーが出る

Rails を用いて、画像アップロードをするシステムを作っていた時、画像を連続3回アップロードすると502エラーが出る場合があった。

これは、メモリの使用量が大きくなり、pod 内のプロセスがkill されていたことが原因だった。
Ruby on Rails のメモリ消費が大きいというのもあるが、Image Magick を使った画像のリサイズ処理は多少重い処理で、メモリ消費、CPU消費どちらも大きい。

CPUのlimitによって処理が遅くなり、メモリのlimitによってプロセスが終了する。
pod 自体は Terminate されていないように見えたので、原因になかなか気がつかなかった。


ログを見るとこのようになっている。

Memory cgroup out of memory: Kill process 1117697 (bundle) score 1802 or sacrifice child Killed process 1117697 (bundle) total-vm:950788kB, anon-rss:340148kB, file-rss:14348kB, shmem-rss:0kB
[Note] Aborted connection 36967 to db: 'hoge_db' user: 'hoge' host: 'cloudsqlproxy~34.00.100.04' (Got an error reading communication packets)
[error] 7#7: *7447 upstream prematurely closed connection while reading response header from upstream, client: 10.146.15.198, server: _, request: "POST /admin/datas/1/ HTTP/1.1", upstream: "http://10.0.15.103:3000/admin/datas/1/", host: "***.com", referrer: "https://***.com/datas/1/edit/"
Client closed local connection on 10.40.4.24:3306
[error] 7#7: *5400 client intended to send too large body: 2328315 bytes, client: 10.40.4.181, server: _, request: "GET /errors/5xx.html HTTP/1.0", host: "cluster-ip-stg-hoge", referrer: "https://***/datas/1/edit/"

CPU は limit を超えても、killされず処理が遅くなるだけだが、memory は kill されるのは間違えないように。

          resources:
            requests:
              cpu: 50m
              memory: 230Mi
            limits:
              cpu: 500m
              memory: 800Mi

Android で スタティックライブラリを利用する

Android の開発時に、C++などで作られた 静的ライブラリ〜.a や、共有ライブラリ〜.so を利用したい時。

まず、ビルド設定方法は1つではない。CMakeLists.txt を使う方法、Android.mk を使う方法と、やり方が選べる。

また、 so ファイルは、アプリケーションコードからロードできるが、 a ファイルは直接ロードできず、so を経由して読み込むことになる。

選択する言語は、Java・Kotolin どちらでも全く問題ない。
ドキュメントだけでは理解できない部分があったのでまとめた。

公式のドキュメントはこちら。
プロジェクトへの C / C++ コードの追加  |  Android Developers

C、C++側の書き方はこちらを参照。
Java Native Interface仕様の目次

Android NDK環境のインストール

Android Studio を起動し、
Android SDK Manager > SDK Toolsタブ と進む。
CMake、LLDB、NDK を選択しインストールする。

プロジェクト作成

ここからは通常通り新規作成をするが、Choose your project で、Native C++ を選択する。
他のサイトでは、Include C++ support のチェックを入れるように書かれているが、その設定はもう無くなっており、必要ない。

プロジェクト内ファイルの説明

cpp フォルダの中に、CMakeLists.txt と、native-lib.cpp ファイルがある。
この2ファイルをいじるだけで、ビルドし、理解する分には十分です。

CMakeLists.txt の書き方

関数の引数は、スペースや改行で区切るイメージ。

共有ライブラリを作成時(C++をそのまま呼び出す場合)

CMakeLists.txt

# ライブラリのルート
set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. )
# ライブラリの出力先
# ANDROID_ABIで、対応するプロセッサごとにフォルダを分ける
set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} )

# ライブラリのソースファイル
# add_library( ライブラリ名 SHARED ソースファイル )
add_library( native-lib SHARED native-lib.cpp )

# 共有ライブラリ 出力先を指定
# set_target_properties( ライブラリ名 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} )
set_target_properties( native-lib PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} )

# ライブラリを変数に定義
# 必須ではないが、このような書き方が出来る
# find_library( 定義名 ライブラリ名... )
find_library( log-lib log )

# 依存ファイルのリンク
# native-lib.cpp の中で、${log-lib} を利用してプログラムをしている場合
# target_link_libraries( ライブラリ名 依存ファイル... )
target_link_libraries( native-lib ${log-lib} )
静的ライブラリを作成する時

CMakeLists.txt

# ライブラリのルート
set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. )
# ライブラリの出力先設定
set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} )

# ライブラリのソースファイル
# add_library( ライブラリ名 STATIC ソースファイル )
add_library( native-lib STATIC native-lib.cpp )

# 静的ライブラリ 出力先を指定
# set_target_properties( ライブラリ名 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR} )
set_target_properties( native-lib PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR} )

build.gradle
リンクしない STATIC ライブラリはビルドされないため、常にビルドさせる設定

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                # targets 'ライブラリ名'
                targets 'native-lib'
            }
        }
    }
}
共有ライブラリを利用する時

読み込めばC++の関数が呼び出せる。

CMakeLists.txt

# ライブラリのルート
set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. )
# ライブラリの出力先設定
set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} )

# ライブラリが外部参照である事を指定
add_library( native-lib SHARED IMPORTED )
# ライブラリファイルの位置を指定
set_target_properties( native-lib PROPERTIES IMPORTED_LOCATION ${OUTPUT_DIR}/libanylib.so )

*.java

class Hoge {
    static {
        System.loadLibrary("native-lib");
    }
}
静的ライブラリを利用する時

流れとしては、一度 静的ライブラリを元に、共有ライブラリを作成し、共有ライブラリを読み込むことになる。
なので、上記の方法を全て把握する必要がある。

CMakeLists.txt

# ライブラリのルート
set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. )
# ライブラリの出力先
set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} )

# ライブラリが外部参照である事を指定
add_library( anylib STATIC IMPORTED )
# ライブラリファイルの位置を指定
set_target_properties( anylib PROPERTIES IMPORTED_LOCATION ${OUTPUT_DIR}/libanylib.a )

# 共有ライブラリの生成
add_library( native-lib SHARED native-lib.cpp )
# 共有ライブラリ 出力先を指定
set_target_properties( native-lib PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} )

# ライブラリのインクルードファイルの参照先
target_include_directories( native-lib PRIVATE ${LIB_ROOT}/include )

# ライブラリを変数に定義
find_library( log-lib log )

# 依存ファイルのリンク
# native-lib.cpp の中で、anylibと、${log-lib} を利用してプログラムをしている場合
target_link_libraries(
        native-lib
        anylib
        ${log-lib} )

*.java

class Hoge {
    static {
        System.loadLibrary("native-lib");
    }
}

Google Cloud Storage 静的ウェブサイトのホスティング 時、 index.html へリダイレクトされる

静的ウェブサイトのホスティング  |  Cloud Storage  |  Google Cloud

gcs を利用し、 websiteホスティングした時に、
スラッシュ無しのパスへアクセスしたとき、/index.html が存在する場合、/index.html が付加されたURLへリダイレクトしてしまう。
しかも、CNAME (正確には Host ヘッダー)に指定した Host でリダイレクトする。

Nginx などで Proxy した場合は、特に問題となる。

そのため、リダイレクトが発生した場合にRedirect を書き換える必要がある。
~ は正規表現を利用する宣言。
example.com を CNAME とした場合の設定。

    set $proto $scheme;
    if ($http_x_forwarded_proto = "https")  { set $proto "https"; } # ELB, GCLB
    if ($http_cloudfront_forwarded_proto = "https")  { set $proto "https"; } # CloudFront

    # ...
 
    resolver 8.8.8.8 valid=60s ipv6=off; # 余談だが、ipv6ではアクセスできない

    proxy_pass http://c.storage.googleapis.com;
    proxy_redirect ~example.com\/(.*)\/index.html $proto://$http_host/$1/$is_args$args; # google cloud storage

    proxy_set_header Host example.com;

CloudFront の Origin に API Gateway を入れた場合に リダイレクトが発生する

API GatewayAPI を作った場合など、そのURLをCloudFrontのOriginに指定したい時がある。

その時に、Origin Protocol Policy を、HTTP Only にしていると、リダイレクトが発生する。HTTPS Onlyにする必要がある。

Vue のファイル出力先を変更する

Vue の デフォルトの出力フォルダは、dist フォルダになっているが、他のフレームワークと共に使うような時、出力先を変えたい場合がある。
さらに出力先を公開ディレクトリ(/public)のサブディレクトリ(/public/webview)にしたい場合の vue.config.js の設定方法。

/
├── frontend
│   ├── vue.config.js
│   ├── babel.config.js
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   └── src
└── public
    └── webview

$ vue.config.js

module.exports = {
  outputDir:'../public/webview', // ファイルの出力先ルート
  publicPath: './webview', // index.html などの出力されるファイルに書き込まれる、ルートとなるディレクトリ
  pages: { // 特に今回書く必要はない
    index: {
      entry: 'src/main.js',
      template: 'public/index.html',
      filename: 'index.html', // webview フォルダからの相対パス
    }
  }
}

GKE Kubernetes で 、無料で https を利用する

GCPでは、https にて通信するための、マネージドな証明書を発行する仕組みがあるため、とても簡単に利用できます。
GKEで利用するためには、 Ingress に annotations を追加するだけです。

まずマネージドな 証明書を作成
$ gcloud beta compute ssl-certificates create www-my-domain-com --domains www.my-domain.com
$ gcloud beta compute ssl-certificates create my-domain-com --domains my-domain.com
annotations に追加
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: basic-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: my-ip
    ingress.gcp.kubernetes.io/pre-shared-cert: "my-domain-com, www-my-domain-com"
spec:
  rules:
    - host: my-domain.com
      http:
        paths:
          - backend:
              serviceName: fuga-nodeport
              servicePort: 80
    - host: www.my-domain.com
      http:
        paths:
          - backend:
              serviceName: hoge-nodeport
              servicePort: 8001
  backend:
    serviceName: web-nodeport
    servicePort: 80

これでしばらくすれば、認証まで行われ、https でアクセスできるようになります。

GKE Kubernetes で Redis を利用する

GCP で フルマネージドのインメモリ データストア を使いたい場合、Memorystore for Redis を利用することになる。

Redis インスタンスに接続するためには、同じリージョンに配置され、同じネットワークを使用するGoogle Kubernetes Engine クラスタである必要がある。グローバルIPは無いということです。

そこで、Kubernetes の Cluster は、IP エイリアス を有効にして作成する必要があります。作成済みの場合は、削除して作り直しが必要です。

gcloud container clusters create myappname  --machine-type=g1-small --num-nodes=1 --enable-ip-alias

cloud.google.com

あとは Redis の IP アドレス に接続するだけです。