2020/05/20

openprojectをGKEで導入する

k8sgkeopenproject

概要

Titleの通りですが、OSSのプロジェクト管理ツールであるopenprojectをkubernetes上にデプロイをして運用を開始しました。
以前はasanaを使っていたのですが、セキュリティ的な観点やコストなどを検討した結果こちらに載せ替えることにしました。
実は別プロジェクトで、運用実績があるのですが、使い勝手がよかったため、本プロジェクトにも導入することを決めました。

実施事項

  1. VPCを追加
  • k8s clusterで利用するため、pod用とservice用に2つのセカンダリIP Rangeを作成するのがポイント
  1. Cloud NATを作成
  • Google Cloud Routerを作成して、No.1で作ったSubnetに適用します
  • Elastic IP AddressをNAT用に作成します
  1. k8s clusterを作成します
  • 限定公開クラスタにして、特定のIPからしかk8sのmasterにアクセスできないようにします
    • こうしておくと、勝手にNo.2で作成したNATが適用され、このクラスタ内部から、インターネットへの外向きの通信ができるようになります
  • このクラスタには他にも種々のmanagement用サービスをdeployするため、1vCPU + 6GBのマシン3台構成をイニシャルの設定としておきます
  • ネットワークタグを指定しておき、後々Firewallの設定を管理し易くしておきます
    • こうすることで、このネットワークタグが自動で、ノードのインスタンスにつきます
  1. openprojectをk8sにデプロイします

k8sのデプロイ内容詳細

事前準備

  • openproject用のpostgresql及びupload file用のcompute diskを作成しておきます。(それぞれ20GBと10GBほど)
  • openprojectのweb server(nginx)のためのelastic ip addressを作成しておきます。
  • sendgridでapi keyを発行しておきます。(メール送信用です)

k8sへのデプロイ設定ファイル及び順序

namespace

apiVersion: v1
kind: Namespace
metadata:
  name: openproject

secrets

apiVersion: v1
kind: Secret
metadata:
  name: openproject
  namespace: openproject
type: Opaque
data:
  secret-key-base: <base64-encoded-string-here>
---
apiVersion: v1
kind: Secret
metadata:
  name: postgresql
  namespace: openproject
type: Opaque
data:
  password: <base64-encoded-string-here>
---
apiVersion: v1
kind: Secret
metadata:
  name: sendgrid
  namespace: openproject
type: Opaque
data:
  # 事前準備で作成した sendgrid の secret keyをbase64エンコードします
  apiKey: <base64-encoded-string-here>
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: internal-tls
  namespace: openproject
data:
  # nginxで使うsslのcrtファイルの中身をbase64エンコードします
  tls_certificate_chain: <base64-encoded-string-here>
  # nginxで使うsslのkeyファイルの中身をbase64エンコードします
  tls_certificate_key: <base64-encoded-string-here>
  # you can generate tls_dhparam by `openssl dhparam 2048 -out tls_dhparam`
  tls_dhparam: <base64-encoded-string-here>

config map

apiVersion: v1
kind: ConfigMap
metadata:
  name: openproject
  namespace: openproject
data:
  rails-cache-store: "memcache"
  cache-server: "memcached:11211"
  database-url: "postgres://openproject:<psqlpasswordhere>@postgresql/openproject"
  smtp-domain: "openproject.example.com"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgresql
  namespace: openproject
data:
  user: openproject
  db-name: openproject

postgresql

apiVersion: v1
kind: PersistentVolume
metadata:
  name: openproject-postgresql
spec:
  storageClassName: openproject-postgresql
  capacity:
    storage: 20Gi # <- 事前準備で作成した compute disk のサイズ
  accessModes:
    - ReadWriteOnce
  gcePersistentDisk:
    pdName: openproject-postgresql # <- 事前準備で作成した compute disk の名前
    fsType: ext4

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgresql
  namespace: openproject
spec:
  storageClassName: openproject-postgresql
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql
  namespace: openproject
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgresql
  template:
    metadata:
      name: postgresql
      labels:
        app: postgresql
        node: management-suite
    spec:
      containers:
        - name: postgresql
          image: postgres:9.5.6-alpine
          env:
            - name: POSTGRES_USER
              valueFrom:
                configMapKeyRef:
                  name: postgresql
                  key: user
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgresql
                  key: password
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: postgresql
                  key: db-name
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          ports:
            - name: postgres
              containerPort: 5432
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: postgresql
      volumes:
        - name: postgresql
          persistentVolumeClaim:
            claimName: postgresql
---
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: openproject
spec:
  ports:
    - name: postgres
      port: 5432
      targetPort: postgres
  selector:
    app: postgresql

memcached

apiVersion: apps/v1
kind: Deployment
metadata:
  name: memcached
  namespace: openproject
spec:
  replicas: 1
  selector:
    matchLabels:
      app: memcached
  template:
    metadata:
      name: memcached
      labels:
        app: memcached
        node: management-suite
    spec:
      containers:
        - name: memcached
          image: memcached:1.5.22-alpine
          ports:
            - containerPort: 11211
              protocol: TCP
            - containerPort: 11211
              protocol: UDP
---
apiVersion: v1
kind: Service
metadata:
  name: memcached
  namespace: openproject
spec:
  ports:
    - protocol: TCP
      port: 11211
      targetPort: 11211
  selector:
    app: memcached

openproject app

apiVersion: v1
kind: PersistentVolume
metadata:
  name: openproject-assets
spec:
  storageClassName: openproject-assets
  capacity:
    storage: 10Gi # <- 事前準備で作成した compute disk のサイズ
  accessModes:
    - ReadWriteOnce
  gcePersistentDisk:
    pdName: openproject-assets # <- 事前準備で作成した compute disk の名前
    fsType: ext4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: openproject-assets
  namespace: openproject
spec:
  storageClassName: openproject-assets
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openproject
  namespace: openproject
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openproject
  template:
    metadata:
      name: openproject
      labels:
        app: openproject
        node: management-suite
    spec:
      securityContext:
        fsGroup: 1000 # GID of app user
      containers:
        - name: openproject
          image: openproject/community:10.3.1
          env:
            - name: SECRET_KEY_BASE
              valueFrom:
                secretKeyRef:
                  name: openproject
                  key: secret-key-base
            - name: RAILS_CACHE_STORE
              valueFrom:
                configMapKeyRef:
                  name: openproject
                  key: rails-cache-store
            - name: OPENPROJECT_CACHE__MEMCACHE__SERVER
              valueFrom:
                configMapKeyRef:
                  name: openproject
                  key: cache-server
            - name: DATABASE_URL
              valueFrom:
                configMapKeyRef:
                  name: openproject
                  key: database-url
            - name: EMAIL_DELIVERY_METHOD
              value: "smtp"
            - name: SMTP_ADDRESS
              value: "smtp.sendgrid.net"
            - name: SMTP_PORT
              value: "2525"
            - name: SMTP_DOMAIN
              valueFrom:
                configMapKeyRef:
                  name: openproject
                  key: smtp-domain
            - name: SMTP_AUTHENTICATION
              value: "login"
            - name: SMTP_ENABLE_STARTTLS_AUTO
              value: "true"
            - name: SMTP_USER_NAME
              value: "apikey"
            - name: SMTP_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: sendgrid
                  key: apiKey
            - name: IMAP_ENABLED
              value: "false"
          ports:
            - name: openproject
              containerPort: 8080
          volumeMounts:
            - mountPath: /var/openproject/assets
              name: openproject-assets
      volumes:
        - name: openproject-assets
          persistentVolumeClaim:
            claimName: openproject-assets
---
apiVersion: v1
kind: Service
metadata:
  name: openproject
  namespace: openproject
spec:
  ports:
    - name: openproject
      port: 8080
      targetPort: openproject
  selector:
    app: openproject

nginx

deploymentは割愛しますが、openproject serviceに対してproxyするnginxのDockerを作成して、デプロイします。
secretでデプロイしたtlsの設定ファイルを使用してSSLを有効にします。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: openproject
spec:
  type: LoadBalancer
  loadBalancerIP: <XXX.XXX.XXX.XXX> # <- 事前準備で作成した外部IPアドレスの値
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https
  selector:
    app: nginx

これらのdeploy後に、内部DNSにAレコードを追加して対応完了です。