やる気がストロングZERO

やる気のストロングスタイル

nekone.loveをkubernetesで動かしてみた

勉強とか実験とかのためにネコ画像集めてるサイトを運用してる。

https://nekone.love

元々普通のVM上でdocker-composeで運用してたが、これをGoogle Kubernetes Engine (GKE)に引っ越ししてみた。

やるまでは「docker-composeでいいじゃん」とか思ってたけど、k8sは色々とサービス運用で必要な機能が揃っていて、プロとしてコンテナ運用するならこれをマスターしないとならないと思うようになった。

docker-composeは「開発環境でコンテナを動作させるためには便利」というような認識になった。

GKEを使用

kubernetesの勉強のためにクラスター構築をすること自体の難易度が高かった為マネージドサービスを使った。
(アドバイスもあったし、少しやってみて確かに難しかった)

GKEなら1年間3万円までの無料枠があったので採用した。

コンテナ構成

アプリケーションサーバーコンテナ

Railsで組んだアプリケーションが載っているコンテナ。
また、猫画像収集バッチ処理がrakeタスクで実装されていてcronで定期実行されている。

本当はこのバッチ処理は別コンテナに分離すべきだったがまだ出来てない。

このコンテナを複数つくると、各コンテナでバッチ処理が同時に動いてしまうので水平スケールができない。。

DBコンテナ

postgresqlが実行されているコンテナ。
データファイルがvolumeで永続化されている。
レプリケーションとかバックアップとかの設定は全くしてない。
本当はマネージドRDSを使うのが正解なんだと思う。

リバースプロキシコンテナ

ssl対応用のnginxが動いているリバースプロキシコンテナ。
k8sとかGKEになんかssl用の機能があって、もしかしたら不要だったりするのかも、、とか思ってる。

設定ファイル類

アプリケーションサーバーコンテナ

deployment.yaml

注意: 多分dbパスワードとか、RAILS_MASTER_KEYとかは環境変数で渡すのではなく、Secretを使って渡すべきだと思う。
今回はとにかく「動かすこと」を目的にしたので端折った。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nekoneko-app-deployment
spec:
  replicas: 1  # バッチ処理が入ってるから、レプリカ数を増やせない。。
  selector:
    matchLabels:
      app: nekoneko-rails-pod
  template:  # pod定義をここでやってる
    metadata:
      labels:
        app: nekoneko-rails-pod
    spec:
      containers:
      - name: nekoneko-rails
        image: asia.gcr.io/{your-project-name}/nekoneko_app:v0.0.4 # googleのコンテナレジストリに上がってるimageを指定
        env: # 環境変数類
        - name: DB_HOST
          value: nekoneko-db-service.default.svc.cluster.local # k8sが内部DNSで名前解決する。独自の命名ルールがある。
        - name: DB_PORT
          value: "5432"
        - name: DB_USER_NAME
          value: db_user_name
        - name: DB_PASSWORD
          value: **********
        - name: DB_DEVELOPMENT_DB_NAME # この辺はproduction用だけ渡しておけばよかったかも。
          value: nekoneko_dev
        - name: DB_TEST_DB_NAME
          value: nekoneko_test
        - name: DB_PRODUCTION_DB_NAME
          value: nekoneko
        - name: SITE_NAME # これはこのrailsアプリケーションの事情で渡してるやつ
          value: nekoneko
        - name: RAILS_MASTER_KEY # railsのproduction環境で必要なやつ
          value: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
        - name: RAILS_LOG_TO_STDOUT # railsログを標準出力に吐くようにする。(コンテナ内にファイルで吐かれてもコンテナと共に消え去るので)
          value: "1"
        ports:
        - containerPort: 80

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nekoneko-service
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: nekoneko-rails-pod

DBコンテナ

statefulset.yaml

注意: 多分dbパスワードとかは環境変数で渡すのではなく、Secretを使って渡すべきだと思う。
今回はとにかく「動かすこと」を目的にしたので端折った。

これはdeploymentではなくstatefulsetを使ってる。
dbなど状態を持つようなコンテナはstatefulsetを使うらしい。

以前これをdeploymentで作ってた時、変更をデプロイしようとしたら失敗した。
原因はvolumeのマウント部分で、旧コンテナとのマウントが外れる前に新コンテナにマウントしようとして「同時にマウントはできない」とエラーが出た。
statefulsetにすることで、旧コンテナからのマウントを外してから新コンテナにマウントされるようになった。
ただし、ダウンタイムは発生するようになった。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nekoneko-db-statefulset
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nekoneko-db-pod
  serviceName: nekoneko-db-service
  template:
    metadata:
      labels:
        app: nekoneko-db-pod
    spec:
      volumes:
      - name: db-volume
        persistentVolumeClaim:
          claimName: nekoneko-disk
      containers:
      - name: db
        image: postgres:12
        volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: db-volume
        env:
        - name: POSTGRES_USER
          value: db_user_name
        - name: POSTGRES_DB
          value: postgres
        - name: POSTGRES_PASSWORD
          value: ****************
        - name: PGDATA
          value: /var/lib/postgresql/data/db_files

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nekoneko-db-service
spec:
  selector:
    app: nekoneko-db-pod
  ports:
  - protocol: TCP
    port: 5432
    targetPort: 5432

volume.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nekoneko-disk
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

リバースプロキシコンテナ

statefulset.yaml

注意: 多分証明書ファイルはvolumeで扱うのではなく、Secretを使って渡すべきだと思う。
今回はとにかく「動かすこと」を目的にしたので端折った。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: reverce-proxy-statefulset
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reverce-proxy-pod
  serviceName: reverce-proxy-service
  template:
    metadata:
      labels:
        app: reverce-proxy-pod
    spec:
      volumes:
      - name: reverce-proxy-volume
        persistentVolumeClaim:
          claimName: reverce-proxy-pvc
      containers:
      - name: reverce-proxy
        image: asia.gcr.io/{your-project-name}/reverse_proxy_server:v0.0.2
        volumeMounts:
        - name: reverce-proxy-volume
          mountPath: /etc/letsencrypt
        ports:
        - containerPort: 80
        - containerPort: 443

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: reverce-proxy-service
spec:
  type: LoadBalancer # この設定でGKEがロードバランサーを用意してグローバルIPを振ってくれる
  ports:
  - port: 80
    name: http
    targetPort: 80
    protocol: TCP
  - port: 443
    name: https
    targetPort: 443
    protocol: TCP
  selector:
    app: reverce-proxy-pod

volume.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: reverce-proxy-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi

もうすぐ無料枠が終わりそう

1ヶ月運用で16000円くらいかかってた。無料枠が尽きると共にnekone.loveサービス終了かも。。