GKE Kubernetes で Ingress が 503 を返す

GKE を利用時に、GKE Ingress を利用した時に、どう考えても設定が正しいのに 503 が帰る場合があった。
注意しなければいけないポイントは、

  • ヘルスチェックのURLは200を返さなければいけない (当然BASIC認証もかかっていてはいけない)
  • ヘルスチェックのURLを変更するには、Deployment の readinessProbe を設定する必要がある
  • Ingress を再作成しなければ、設定変更が反映されない

ヘルスチェックのURLは200を返さなければいけない

Ingress は Service が有効かを判定するために、アクセスログを見ればわかるが、デフォルトだと / にアクセスしてくる。このとき200を返していなければならない。

ヘルスチェックのURLを変更するには、Deployment の readwinessProbe を設定する必要がある

わかりにくいが、Ingress が確認する readinessProbe は、Ingress が参照する Service の中で利用している、Deployment 側の readinessProbe が継承される。これがとてもわかりにくい。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: gcr.io/PROJECT_NAME/web:0.1
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /healthcheck
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-name
  annotations:
    kubernetes.io/ingress.global-static-ip-name: my-ip
spec:
  backend:
    serviceName: web-nodeport
    servicePort: 80

Ingress を再作成しなければ、設定変更が反映されない

途中で Deployment の設定を変更しても / にずっとアクセスが来ていたが、ingress を作り直すことで反映された。
これは公開後に変更した場合はかなり困ることになるはず。

$ kubectl delete ing ingress-name
$ kubectl apply -f sample.yml

GKE Kubernetes で CloudSQL を利用する

Cloud SQL を利用するためには、直接接続するのではなく、 Cloud SQL Proxy を利用します。
Cloud SQL Proxy は Docker image として公開されているため、こちらを使います。


Googleのドキュメント通りに設定すれば通信出来ます。
Pod 内に Proxy の Container を含める構成になっており、この構成をサイドカーパターンと呼びます。

cloud.google.com

サンプル通りのやり方

ポイントは1つだけ、CloudSQL Client の役割をつけたサービスアカウントを作成し、cloudsql-instance-credentials などの名前で json を保存します。

kubectl create secret generic cloudsql-instance-credentials --from-file credentials.json=./secret/projectname-1234567890.json

簡単にするため、ユーザー名パスワードは埋め込みました。
Pod内のContainer同士は、127.0.0.1 でアクセスできます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
        - name: app-backend
          image: gcr.io/project_desu/app:0.9
          ports:
            - containerPort: 3000
          env:
            - name: PORT
              value: '3000'
            - name: DATABASE_HOST
              value: 127.0.0.1
            - name: DATABASE_DATABASE
              value: hoge
            - name: DATABASE_USER_NAME
              value: name_desu
            - name: DATABASE_PASSWORD
              value: password_desu
        - image: gcr.io/cloudsql-docker/gce-proxy:1.12
          name: cloudsql-proxy
          ports:
            - containerPort: 3306
          command: ["/cloud_sql_proxy", "--dir=/cloudsql",
                    "-instances=PROJECT_NAME:asia-northeast1:INSTANCE_NAME=tcp::3306",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
            - name: ssl-certs
              mountPath: /etc/ssl/certs
            - name: cloudsql
              mountPath: /cloudsql
      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
        - name: cloudsql
          emptyDir:

Service を使い Pod を分ける方法

アプリケーションの数だけ Container が増えることになるため、リソースが限られている時など上記の方法を避けたい場合があります。Serviceを利用しアクセスするためには下記のように設定します。

TCPにバインドするIPアドレス 0.0.0.0 を指定することがポイントです。
これによって、Service経由のホスト名db-cluster-ip で Podにアクセス出来ます。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: db-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - image: gcr.io/cloudsql-docker/gce-proxy:1.12
          name: cloudsql-proxy
          ports:
            - containerPort: 3306
          command: ["/cloud_sql_proxy", "--dir=/cloudsql",
                    "-instances=PROJECT_NAME:asia-northeast1:INSTANCE_NAME=tcp:0.0.0.0:3306",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
            - name: ssl-certs
              mountPath: /etc/ssl/certs
            - name: cloudsql
              mountPath: /cloudsql

      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
        - name: cloudsql
          emptyDir:
apiVersion: v1
kind: Service
metadata:
  name: db-cluster-ip
spec:
  type: ClusterIP
  ports:
    - port: 3306
      targetPort: 3306
  selector:
    app: db

Mysql8.0 で パスワードを無しに設定したい場合

パスワードを空に設定しようにも、バリデーションが働き、空に設定できません。
バリデーション機能を無効化することで、自由なパスワードを設定できるようにします。

無効化
UNINSTALL COMPONENT 'file://component_validate_password';
有効化
INSTALL COMPONENT 'file://component_validate_password';

無効化したあと、

set password for root@localhost = '';


Mysql8.0.4 以降では、コンポーネントという仕組みによって、validate_password が機能しています。よってコンポーネントを無効化することになります。
それ以前は、プラグインの仕組みによって、validate_password が実装されていたので、また設定方法が変わったことになります。

ActiveJob で active_elastic_job を利用時、設定が反映されない

active_slastic_job を利用しているとき、Rails のバージョンを変更したところ、

I, [2018-10-12T16:21:11.029048 #653]  INFO -- : [d8e52f46-4c12-40b5-ab99-f593f6306a95] Started POST "/" for 127.0.0.1 at 2018-10-12 16:21:11 +0000
F, [2018-10-12T16:21:11.029747 #653] FATAL -- : [d8e52f46-4c12-40b5-ab99-f593f6306a95]   
F, [2018-10-12T16:21:11.029800 #653] FATAL -- : [d8e52f46-4c12-40b5-ab99-f593f6306a95] ActionController::RoutingError (No route matches [POST] "/"):
F, [2018-10-12T16:21:11.029837 #653] FATAL -- : [d8e52f46-4c12-40b5-ab99-f593f6306a95]   
F, [2018-10-12T16:21:11.029906 #653] FATAL -- : [d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) actionpack/lib/action_dispatch/middleware/debug_exceptions.rb:73:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) actionpack/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) railties/lib/rails/rack/logger.rb:38:in `call_app'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) railties/lib/rails/rack/logger.rb:26:in `block in call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) activesupport/lib/active_support/tagged_logging.rb:80:in `block in tagged'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) activesupport/lib/active_support/tagged_logging.rb:28:in `tagged'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) activesupport/lib/active_support/tagged_logging.rb:80:in `tagged'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) railties/lib/rails/rack/logger.rb:26:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) actionpack/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) actionpack/lib/action_dispatch/middleware/request_id.rb:27:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rack (2.0.5) lib/rack/method_override.rb:22:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rack (2.0.5) lib/rack/runtime.rb:22:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) actionpack/lib/action_dispatch/middleware/executor.rb:14:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rack (2.0.5) lib/rack/sendfile.rb:111:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] rails (01294c25566a) railties/lib/rails/engine.rb:524:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] puma (3.12.0) lib/puma/configuration.rb:225:in `call'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] puma (3.12.0) lib/puma/server.rb:658:in `handle_request'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] puma (3.12.0) lib/puma/server.rb:472:in `process_client'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] puma (3.12.0) lib/puma/server.rb:332:in `block in run'
[d8e52f46-4c12-40b5-ab99-f593f6306a95] puma (3.12.0) lib/puma/thread_pool.rb:133:in `block in spawn_thread'

このように、No route matches が発生するようになった。
この原因は、config.active_elastic_job.process_jobs この変数がtrueになっていないときに起きる。

$ config/initializers/active_elastic_job.rb

こちらのファイルがロードされるタイミングが、

$ vendor/bundle/ruby/2.5.0/bundler/gems/active-elastic-job-96c6ace4b7f1/lib/active_elastic_job/railtie.rb (これは人によって変わる)

こちらより後にタイミングが変更されていて、設定が反映されなくなってしまっていた。

initializers に書くのはあきらめて、より実行のはやい、 config/environments/*.rb に書くことで解決する。


そもそも active-elastic-job は、最新のRailsに対応していなかったが、こちらのリポジトリで、最新のRailsでも動くように書き換えてあります。
https://github.com/unchi/active-elastic-job

ActiveAdmin の update url に対して、ajax通信すると302リダイレクトし続ける

例えば 管理画面上の action ボタンで、データを更新できるようにした場合ですが、

ActiveAdmin.register Message do
  index do
    script do
      raw <<-'EOS'
        function censor(id, url){
          var elem = document.querySelector('#message_'+id+' > .col-text > span');
          elem.innerText = "ほげほげ";
          $.ajax({ url: url, type: 'PUT', dataType: "json", data: { message: {text: "ほげほげ"} }});
        }
      EOS
    end
    selectable_column
    id_column
    column :text
    column :created_at
    actions do |v|
      item "ほげボタン", "javascript:censor(#{v.id}, #{raw admin_message_path(v).to_json});", class: "view_link member_link"
    end
  end
end

dataType json を指定しないと、302 リダイレクトループになってしまいます。


このイシューと同じ状況です。
https://github.com/activeadmin/activeadmin/issues/2345#issuecomment-41262778

Carrierwave から ActiveStrage に乗り換えたときに、予想外だったこと

route の一番下に、404用のアクションを定義していると、画像が表示されない

$ config/route.rb

# ...

get '*path', to: 'application#error_404'

これは、active storage が定義する 以下よりも先に、上記が処理されているため。

get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob, internal: true

host を指定してファイルパスを取得したい

url_for を使用してフルパスを取得するように書かれていることが多いが、このメソッドはオプションでhostを指定できない。

polymorphic_url を利用すると、以下のようにすれば設定できる。

polymorphic_url(hoge.image, host: "http://*****/")

同時にActiveJob を使っていると、データのアップロードが非同期に自動で切り替わる

config.active_job.queue_adapter を inline ではなく、別の例えば active_elastic_job などにしていると、
内部で ActiveStorage::AnalyzeJob が利用されているため、勝手に非同期となる。
そのときの デフォルトの quere 名が default のため、このままではエラーになってしまう。

config.active_storage.queue で 別の quere名を設定する。

Alexa 開発時、スキルの呼び出し名の変更が反映されない

2018/09/21現在、スキルの呼び出し名を変更しても、それだけでは反映されない。
ストアに公開するための認定時にも、古い呼び出し名のまま動作確認が行われてしまう。

これを反映させるには、一度テストを無効にして、再度有効にすることで反映される。

f:id:unching-star:20180921202922p:plain

それにしても、メールで送られてくる、Alexaスキル 認定に関するフィードバックは、
指摘に関する再現方法も細かく記載され、修正方法まで書かれているため、とてもわかりやすい。
Apple も見習って欲しいですね。