You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2022/11/01 02:48:24 UTC

[incubator-devlake-helm-chart] branch main updated: Add support postgres database. (#45)

This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake-helm-chart.git


The following commit(s) were added to refs/heads/main by this push:
     new 68037f1  Add support postgres database. (#45)
68037f1 is described below

commit 68037f13f31e1f4ef1fb2b870adb6b453687582d
Author: Ji Bin <ma...@live.com>
AuthorDate: Tue Nov 1 10:48:20 2022 +0800

    Add support postgres database. (#45)
    
    Signed-off-by: Ji Bin <ma...@live.com>
    
    Signed-off-by: Ji Bin <ma...@live.com>
---
 .github/workflows/deploy-test.yml          |  32 ++++----
 .github/workflows/yaml-lint.yml            |   2 +-
 charts/devlake/Chart.yaml                  |   2 +-
 charts/devlake/README.md                   |  93 ++++++++++++-----------
 charts/devlake/templates/_helpers.tpl      |  78 ++++++++++++++++++++
 charts/devlake/templates/configmaps.yaml   |  71 ++++++++++++++----
 charts/devlake/templates/deployments.yaml  |  57 +++++++--------
 charts/devlake/templates/ingresses.yaml    |   4 +-
 charts/devlake/templates/services.yaml     |  25 ++++++-
 charts/devlake/templates/statefulsets.yaml | 114 +++++++++++++++++++++++++----
 charts/devlake/values.yaml                 |  51 ++++++++++++-
 11 files changed, 402 insertions(+), 127 deletions(-)

diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml
index 3c373ae..9d83e64 100644
--- a/.github/workflows/deploy-test.yml
+++ b/.github/workflows/deploy-test.yml
@@ -23,11 +23,11 @@ on:
     branches:
       - main
     paths:
-      - deployment/helm/**
+      - charts/devlake/**
       - .github/workflows/deploy-test.yml
   pull_request:
     paths:
-      - deployment/helm/**
+      - charts/devlake/**
       - .github/workflows/deploy-test.yml
 
 jobs:
@@ -36,7 +36,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        database_type: ["mysql-builtin", "mysql-external"]
+        database_type: ["mysql-builtin", "mysql-external", "pgsql-builtin"]
     steps:
       - name: Creating kind cluster
         uses: container-tools/kind-action@v1
@@ -53,23 +53,13 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v2
 
-      # Currently needs 6mins to build all images, so maybe enabled later in scheduled jobs.
-      # - name: Build container images
-      #   run: |
-      #     docker build -t kind-registry:5000/deploy-test-lake:latest .
-      #     docker build -t kind-registry:5000/deploy-test-grafana:latest grafana
-      #     docker build -t kind-registry:5000/deploy-test-ui:latest config-ui
-      #     docker push kind-registry:5000/deploy-test-lake:latest
-      #     docker push kind-registry:5000/deploy-test-grafana:latest
-      #     docker push kind-registry:5000/deploy-test-ui:latest
-
       - name: Helm install devlake
         if: matrix.database_type == 'mysql-external'
         run: |
           helm repo add bitnami https://charts.bitnami.com/bitnami
           helm install mysql bitnami/mysql --set auth.rootPassword=admin --set auth.database=lake --set auth.username=merico --set auth.password=merico
           # external mysql at service: mysql
-          helm install --wait --timeout 300s deploy-test deployment/helm \
+          helm install --wait --timeout 300s deploy-test charts/devlake \
             --set service.uiPort=30000 \
             --set mysql.useExternal=true \
             --set mysql.externalServer=mysql \
@@ -82,12 +72,24 @@ jobs:
         run: |
           export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
           echo Node IP: ${NODE_IP}
-          helm install --wait --timeout 300s deploy-test deployment/helm \
+          helm install --wait --timeout 300s deploy-test charts/devlake \
             --set service.uiPort=30000 \
             --set mysql.image.tag=8-debian \
             --set option.localtime=""
           kubectl get pods -o wide
           kubectl get services -o wide
+      
+      - name: Helm install devlake
+        if: matrix.database_type == 'pgsql-builtin'
+        run: |
+          export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
+          echo Node IP: ${NODE_IP}
+          helm install --wait --timeout 300s deploy-test charts/devlake \
+            --set service.uiPort=30000 \
+            --set option.database=pgsql \
+            --set option.localtime=""
+          kubectl get pods -o wide
+          kubectl get services -o wide
 
       # TODO: using some e2e test code to replace it
       - name: Curl with endpoints
diff --git a/.github/workflows/yaml-lint.yml b/.github/workflows/yaml-lint.yml
index 083a921..033af52 100644
--- a/.github/workflows/yaml-lint.yml
+++ b/.github/workflows/yaml-lint.yml
@@ -30,4 +30,4 @@ jobs:
       - name: install latest helm
         run: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
       - name: lint helm chart
-        run: helm lint deployment/helm --strict
+        run: helm lint charts/devlake --strict
diff --git a/charts/devlake/Chart.yaml b/charts/devlake/Chart.yaml
index b361fe6..605e7d2 100644
--- a/charts/devlake/Chart.yaml
+++ b/charts/devlake/Chart.yaml
@@ -29,7 +29,7 @@ type: application
 
 
 # Chart version
-version: v0.14.3
+version: v0.14.3-1
 
 # devlake version
 appVersion: "v0.14.2"
diff --git a/charts/devlake/README.md b/charts/devlake/README.md
index 3542420..924d86f 100644
--- a/charts/devlake/README.md
+++ b/charts/devlake/README.md
@@ -74,44 +74,55 @@ After deployed, visit devlake: https://devlake-0.example.com:8443, and grafana a
 
 Some useful parameters for the chart, you could also check them in values.yaml
 
-| Parameter | Description                                                                            | Default |
-|-----------|----------------------------------------------------------------------------------------|---------|
-| replicaCount  | Replica Count for devlake, currently not used                                          | 1  |
-| mysql.useExternal  | If use external mysql server, set true                                                 |  false  |
-| mysql.externalServer  | External mysql server address                                                          | 127.0.0.1  |
-| mysql.externalPort  | External mysql server port                                                             | 3306  |
-| mysql.username  | username for mysql                                                                     | merico  |
-| mysql.password  | password for mysql                                                                     | merico  |
-| mysql.database  | database for mysql                                                                     | lake  |
-| mysql.rootPassword  | root password for mysql                                                                | admin  |
-| mysql.storage.class  | storage class for mysql's volume                                                       | ""  |
-| mysql.storage.size  | volume size for mysql's data                                                           | 5Gi  |
-| mysql.image.repository  | repository for mysql's image                                                           | mysql  |
-| mysql.image.tag  | image tag for mysql's image                                                            | 8.0.26  |
-| mysql.image.pullPolicy  | pullPolicy for mysql's image                                                           | IfNotPresent  |
-| grafana.image.repository  | repository for grafana's image                                                         | mericodev/grafana  |
-| grafana.image.tag  | image tag for grafana's image                                                          | latest  |
-| grafana.image.pullPolicy  | pullPolicy for grafana's image                                                         | Always  |
-| lake.storage.class  | storage class for lake's volume                                                        | ""  |
-| lake.storage.size  | volume size for lake's data                                                            | 100Mi  |
-| lake.image.repository  | repository for lake's image                                                            | mericodev/lake  |
-| lake.image.tag  | image tag for lake's image                                                             | latest  |
-| lake.image.pullPolicy  | pullPolicy for lake's image                                                            | Always  |
-| ui.image.repository  | repository for ui's image                                                              | mericodev/config-ui  |
-| ui.image.tag  | image tag for ui's image                                                               | latest  |
-| ui.image.pullPolicy  | pullPolicy for ui's image                                                              | Always  |
-| ui.basicAuth.enabled  | If the basic auth in ui is enabled                                                     | false  |
-| ui.basicAuth.user  | The user name for the basic auth                                                       | "admin"  |
-| ui.basicAuth.password  | The password for the basic auth                                                        | "admin"  |
-| service.type  | Service type for exposed service                                                       | NodePort  |
-| service.uiPort  | Service port for config ui                                                             | 32001  |
-| service.ingress.enabled  | If enable ingress                                                                      |  false  |
-| service.ingress.enableHttps  | If enable https                                                                        |  false  |
-| service.ingress.className  | The class name for ingressClass. If leave empty, the default IngressClass will be used | ""  |
-| service.ingress.hostname  | The hostname/domainname for ingress                                                    | localhost  |
-| service.ingress.prefix | The prefix for endpoints, currently not supported due to devlake's implementation      | /  |
-| service.ingress.tlsSecretName  | The secret name for tls's certificate, required when https enabled                     | ""  |
-| service.ingress.httpPort  | The http port for ingress                                                              | 80  |
-| service.ingress.httpsPort  | The https port for ingress                                                             | 443  |
-| option.localtime  | The hostpath for mount as /etc/localtime                                               | /etc/localtime  |
-
+| Parameter                     | Description                                                | Default             |
+| ----------------------------- | ---------------------------------------------------------- | ------------------- |
+| replicaCount                  | Replica Count for devlake, currently not used              | 1                   |
+| mysql.useExternal             | If use external mysql server, set true                     | false               |
+| mysql.externalServer          | External mysql server address                              | 127.0.0.1           |
+| mysql.externalPort            | External mysql server port                                 | 3306                |
+| mysql.username                | username for mysql                                         | merico              |
+| mysql.password                | password for mysql                                         | merico              |
+| mysql.database                | database for mysql                                         | lake                |
+| mysql.rootPassword            | root password for mysql                                    | admin               |
+| mysql.storage.class           | storage class for mysql's volume                           | ""                  |
+| mysql.storage.size            | volume size for mysql's data                               | 5Gi                 |
+| mysql.image.repository        | repository for mysql's image                               | mysql               |
+| mysql.image.tag               | image tag for mysql's image                                | 8.0.31              |
+| mysql.image.pullPolicy        | pullPolicy for mysql's image                               | IfNotPresent        |
+| pgsql.useExternal             | If use external pgsql server, set true                     | false               |
+| pgsql.externalServer          | External pgsql server address                              | 127.0.0.1           |
+| pgsql.externalPort            | External pgsql server port                                 | 3306                |
+| pgsql.username                | username for pgsql                                         | merico              |
+| pgsql.password                | password for pgsql                                         | merico              |
+| pgsql.database                | database for pgsql                                         | lake                |
+| pgsql.storage.class           | storage class for pgsql's volume                           | ""                  |
+| pgsql.storage.size            | volume size for pgsql's data                               | 5Gi                 |
+| pgsql.image.repository        | repository for pgsql's image                               | postgres            |
+| pgsql.image.tag               | image tag for pgsql's image                                | 14.5                |
+| pgsql.image.pullPolicy        | pullPolicy for pgsql's image                               | IfNotPresent        |
+| grafana.image.repository      | repository for grafana's image                             | mericodev/grafana   |
+| grafana.image.tag             | image tag for grafana's image                              | latest              |
+| grafana.image.pullPolicy      | pullPolicy for grafana's image                             | Always              |
+| lake.storage.class            | storage class for lake's volume                            | ""                  |
+| lake.storage.size             | volume size for lake's data                                | 100Mi               |
+| lake.image.repository         | repository for lake's image                                | mericodev/lake      |
+| lake.image.tag                | image tag for lake's image                                 | latest              |
+| lake.image.pullPolicy         | pullPolicy for lake's image                                | Always              |
+| ui.image.repository           | repository for ui's image                                  | mericodev/config-ui |
+| ui.image.tag                  | image tag for ui's image                                   | latest              |
+| ui.image.pullPolicy           | pullPolicy for ui's image                                  | Always              |
+| ui.basicAuth.enabled          | If the basic auth in ui is enabled                         | false               |
+| ui.basicAuth.user             | The user name for the basic auth                           | "admin"             |
+| ui.basicAuth.password         | The password for the basic auth                            | "admin"             |
+| service.type                  | Service type for exposed service                           | NodePort            |
+| service.uiPort                | Node port for config ui                                    | 32001               |
+| service.ingress.enabled       | If enable ingress                                          | false               |
+| service.ingress.enableHttps   | If enable https                                            | false               |
+| service.ingress.className     | Class name for ingressClass. leave empty for using default | ""                  |
+| service.ingress.hostname      | The hostname/domainname for ingress                        | localhost           |
+| service.ingress.prefix        | The prefix for endpoints, currently not used               | /                   |
+| service.ingress.tlsSecretName | The secret name for tls's certificate for https            | ""                  |
+| service.ingress.httpPort      | The http port for ingress                                  | 80                  |
+| service.ingress.httpsPort     | The https port for ingress                                 | 443                 |
+| option.localtime              | The hostpath for mount as /etc/localtime                   | /etc/localtime      |
+| option.database               | The database type, valids: mysql, pgsql                    | mysql               |
diff --git a/charts/devlake/templates/_helpers.tpl b/charts/devlake/templates/_helpers.tpl
index e786642..b4562c3 100644
--- a/charts/devlake/templates/_helpers.tpl
+++ b/charts/devlake/templates/_helpers.tpl
@@ -118,3 +118,81 @@ The mysql port
 {{- 3306 }}
 {{- end }}
 {{- end }}
+
+{{/*
+The pgsql server
+*/}}
+{{- define "pgsql.server" -}}
+{{- if .Values.pgsql.useExternal }}
+{{- .Values.pgsql.externalServer }}
+{{- else }}
+{{- print (include "devlake.fullname" . ) "-pgsql" }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+The pgsql port
+*/}}
+{{- define "pgsql.port" -}}
+{{- if .Values.pgsql.useExternal }}
+{{- .Values.pgsql.externalPort }}
+{{- else }}
+{{- 5432 }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+The database server
+*/}}
+{{- define "database.server" -}}
+{{- if eq .Values.option.database "mysql" }}
+{{- include "mysql.server" . }}
+{{- else if eq .Values.option.database "pgsql" }}
+{{- include "pgsql.server" . }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+The database port
+*/}}
+{{- define "database.port" -}}
+{{- if eq .Values.option.database "mysql" }}
+{{- include "mysql.port" . }}
+{{- else if eq .Values.option.database "pgsql" }}
+{{- include "pgsql.port" . }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+The database url
+*/}}
+{{- define "database.url" -}}
+{{- if eq .Values.option.database "mysql" -}}
+mysql://{{ .Values.mysql.username }}:{{ .Values.mysql.password }}@{{ include "mysql.server" . }}:{{ include "mysql.port" . }}/{{ .Values.mysql.database }}?charset=utf8mb4&parseTime=True
+{{- else if eq .Values.option.database "pgsql" -}}
+postgres://{{ .Values.pgsql.username }}:{{ .Values.pgsql.password }}@{{ include "pgsql.server" . }}:{{ include "pgsql.port" . }}/{{ .Values.pgsql.database }}
+{{- end }}
+{{- end }}
+
+
+{{/*
+The probe for check database connection
+*/}}
+{{- define "common.initContainerWaitDatabase" -}}
+- name: waiting-database-ready
+  image: "{{ .Values.alpine.image.repository }}:{{ .Values.alpine.image.tag }}"
+  imagePullPolicy: {{ .Values.alpine.image.pullPolicy }}
+  command:
+    - 'sh'
+    - '-c'
+    - |
+      until nc -z -w 2 {{ include "database.server" . }} {{ include "database.port" . }} ; do
+        echo wait for database ready ...
+        sleep 2
+      done
+      echo database is ready
+{{- end }}
\ No newline at end of file
diff --git a/charts/devlake/templates/configmaps.yaml b/charts/devlake/templates/configmaps.yaml
index 5cb6ccc..ff79fde 100644
--- a/charts/devlake/templates/configmaps.yaml
+++ b/charts/devlake/templates/configmaps.yaml
@@ -1,31 +1,72 @@
 #
-  # Licensed to the Apache Software Foundation (ASF) under one or more
-  # contributor license agreements.  See the NOTICE file distributed with
-  # this work for additional information regarding copyright ownership.
-  # The ASF licenses this file to You under the Apache License, Version 2.0
-  # (the "License"); you may not use this file except in compliance with
-  # the License.  You may obtain a copy of the License at
-  #
-  #     http://www.apache.org/licenses/LICENSE-2.0
-  #
-  # Unless required by applicable law or agreed to in writing, software
-  # distributed under the License is distributed on an "AS IS" BASIS,
-  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  # See the License for the specific language governing permissions and
-  # limitations under the License.
-  #
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: {{ include "devlake.fullname" . }}-config
 data:
+{{- if eq .Values.option.database "mysql" }}
   MYSQL_USER: "{{ .Values.mysql.username }}"
   MYSQL_PASSWORD: "{{ .Values.mysql.password }}"
   MYSQL_DATABASE: "{{ .Values.mysql.database }}"
   MYSQL_ROOT_PASSWORD: "{{ .Values.mysql.rootPassword }}"
+{{- else if eq .Values.option.database "pgsql" }}
+  POSTGRES_USER: "{{ .Values.pgsql.username }}"
+  POSTGRES_PASSWORD: "{{ .Values.pgsql.password }}"
+  POSTGRES_DB: "{{ .Values.pgsql.database }}"
+{{- end }}
   LOGGING_DIR: "{{ .Values.lake.loggingDir }}"
 {{- if .Values.ui.basicAuth.enabled }}
   ADMIN_USER: "{{ .Values.ui.basicAuth.user }}"
   ADMIN_PASS: "{{ .Values.ui.basicAuth.password }}"
 {{- end }}
+
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "devlake.fullname" . }}-grafana-datasource
+data:
+{{- if eq .Values.option.database "mysql" }}
+  grafana-datasource.yml: |
+    apiVersion: 1
+    datasources:
+      - name: mysql
+        type: mysql
+        url: $MYSQL_URL
+        database: $MYSQL_DATABASE
+        user: $MYSQL_USER
+        secureJsonData:
+          password: $MYSQL_PASSWORD
+        editable: false
+{{- else if eq .Values.option.database "pgsql" }}
+  grafana-datasource.yml: |
+    apiVersion: 1
+    datasources:
+      - name: postgres
+        type: postgres
+        url: $POSTGRES_URL
+        database: $POSTGRES_DB
+        user: $POSTGRES_USER
+        secureJsonData:
+          password: $POSTGRES_PASSWORD
+        jsonData:
+          sslmode: 'disable'
+          postgresVersion: 1000 # support pgsql 10.0 and above
+          timescaledb: false
+{{- end }}
diff --git a/charts/devlake/templates/deployments.yaml b/charts/devlake/templates/deployments.yaml
index b3ac074..2de8b2f 100644
--- a/charts/devlake/templates/deployments.yaml
+++ b/charts/devlake/templates/deployments.yaml
@@ -35,18 +35,7 @@ spec:
         devlakeComponent: grafana
     spec:
       initContainers:
-        - name: waiting-mysql-ready
-          image: "{{ .Values.alpine.image.repository }}:{{ .Values.alpine.image.tag }}"
-          imagePullPolicy: {{ .Values.alpine.image.pullPolicy }}
-          command:
-            - 'sh'
-            - '-c'
-            - |
-              until nc -z -w 2 {{ include "mysql.server" . }} {{ include "mysql.port" . }} ; do
-                echo wait for mysql ready ...
-                sleep 2
-              done
-              echo mysql is ready
+        {{- include "common.initContainerWaitDatabase" . | nindent 8 }}
       containers:
         - name: grafana
           image: "{{ .Values.grafana.image.repository }}:{{ .Values.grafana.image.tag }}"
@@ -65,21 +54,29 @@ spec:
               mountPath: /etc/localtime
               readOnly: true
             {{- end }}
+            - name: {{ include "devlake.fullname" . }}-grafana-datasource-config
+              mountPath: /etc/grafana/provisioning/datasources/datasource.yml
+              subPath: grafana-datasource.yml
           envFrom:
             - configMapRef:
                 name: {{ include "devlake.fullname" . }}-config
           env:
-              - name: GF_SERVER_ROOT_URL
-                value: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
-                - name: MYSQL_URL
-                  value: {{ include "mysql.server" . }}:{{ include "mysql.port" . }}
+            - name: GF_SERVER_ROOT_URL
+              value: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
+            - name: MYSQL_URL
+              value: {{ include "mysql.server" . }}:{{ include "mysql.port" . }}
+            - name: POSTGRES_URL
+              value: {{ include "pgsql.server" . }}:{{ include "pgsql.port" . }}
       volumes:
-          {{- if ne .Values.option.localtime "" }}
-            - name: {{ include "devlake.fullname" . }}-grafana-localtime
-            hostPath:
-              path: {{ .Values.option.localtime }}
-              type: File
+        {{- if ne .Values.option.localtime "" }}
+        - name: {{ include "devlake.fullname" . }}-grafana-localtime
+          hostPath:
+            path: {{ .Values.option.localtime }}
+            type: File
         {{- end }}
+        - name: {{ include "devlake.fullname" . }}-grafana-datasource-config
+          configMap:
+            name: {{ include "devlake.fullname" . }}-grafana-datasource
 
 ---
 # devlake-ui
@@ -110,16 +107,16 @@ spec:
             - configMapRef:
                 name: {{ include "devlake.fullname" . }}-config
           env:
-              - name: DEVLAKE_ENDPOINT
-            # TODO: remove hardcoded `cluster.local`
-                value: {{ include "devlake.fullname" . }}-lake.{{ .Release.Namespace }}.svc.cluster.local:8080
-                - name: GRAFANA_ENDPOINT
-                value: {{ include "devlake.fullname" . }}-grafana.{{ .Release.Namespace }}.svc.cluster.local:3000
+            - name: DEVLAKE_ENDPOINT
+              # TODO: remove hardcoded `cluster.local`
+              value: {{ include "devlake.fullname" . }}-lake.{{ .Release.Namespace }}.svc.cluster.local:8080
+            - name: GRAFANA_ENDPOINT
+              value: {{ include "devlake.fullname" . }}-grafana.{{ .Release.Namespace }}.svc.cluster.local:3000
           volumeMounts:
-              {{- if ne .Values.option.localtime "" }}
-                - name: {{ include "devlake.fullname" . }}-ui-localtime
-                mountPath: /etc/localtime
-                readOnly: true
+            {{- if ne .Values.option.localtime "" }}
+            - name: {{ include "devlake.fullname" . }}-ui-localtime
+              mountPath: /etc/localtime
+              readOnly: true
             {{- end }}
       volumes:
         {{- if ne .Values.option.localtime "" }}
diff --git a/charts/devlake/templates/ingresses.yaml b/charts/devlake/templates/ingresses.yaml
index 092151d..eb79ed7 100644
--- a/charts/devlake/templates/ingresses.yaml
+++ b/charts/devlake/templates/ingresses.yaml
@@ -76,9 +76,9 @@ spec:
               service:
                 name: {{ include "devlake.fullname" . }}-ui
                 port:
-                  number: {{ .Values.service.uiPort }}
+                  number: 4000
               {{- else }}
               serviceName: {{ include "devlake.fullname" . }}-ui
-              servicePort: {{ .Values.service.uiPort }}
+              servicePort: 4000
               {{- end }}
 {{- end }}
diff --git a/charts/devlake/templates/services.yaml b/charts/devlake/templates/services.yaml
index 73e9662..0c9f50d 100644
--- a/charts/devlake/templates/services.yaml
+++ b/charts/devlake/templates/services.yaml
@@ -14,8 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# mysql services
+# database services
 ---
+
+{{- if eq .Values.option.database "mysql" }}
 {{- if not .Values.mysql.useExternal }}
 apiVersion: v1
 kind: Service
@@ -33,6 +35,25 @@ spec:
       port: 3306
       targetPort: 3306
 {{- end }}
+{{- else if eq .Values.option.database "pgsql" }}
+{{- if not .Values.pgsql.useExternal }}
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "devlake.fullname" . }}-pgsql
+  labels:
+    {{- include "devlake.labels" . | nindent 4 }}
+spec:
+  selector:
+    {{- include "devlake.selectorLabels" . | nindent 4 }}
+    devlakeComponent: pgsql
+  ports:
+    - protocol: TCP
+      name: pgsql
+      port: 5432
+      targetPort: 5432
+{{- end }}
+{{- end }}
 
 # grafana services
 ---
@@ -56,8 +77,6 @@ spec:
       nodePort: {{ .Values.service.grafanaPort }}
       {{- end }}
 
-
-
 # devlake services
 ---
 apiVersion: v1
diff --git a/charts/devlake/templates/statefulsets.yaml b/charts/devlake/templates/statefulsets.yaml
index 74a7f82..be5ad05 100644
--- a/charts/devlake/templates/statefulsets.yaml
+++ b/charts/devlake/templates/statefulsets.yaml
@@ -15,7 +15,8 @@
 # limitations under the License.
 #
 ---
-# mysql statefulset
+# database statefulset
+{{- if eq .Values.option.database "mysql" }}
 {{- if not .Values.mysql.useExternal }}
 apiVersion: apps/v1
 kind: StatefulSet
@@ -69,7 +70,6 @@ spec:
           volumeMounts:
             - mountPath: /var/lib/mysql
               name: {{ include "devlake.fullname" . }}-mysql-data
-          volumeMounts:
             {{- if ne .Values.option.localtime "" }}
             - name: {{ include "devlake.fullname" . }}-mysql-localtime
               mountPath: /etc/localtime
@@ -98,14 +98,105 @@ spec:
     - metadata:
         name: {{ include "devlake.fullname" . }}-mysql-data
       spec:
-        accessModes: [ "ReadWriteOnce" ]
+        accessModes: ["ReadWriteOnce"]
         {{- with .Values.mysql.storage.class }}
         storageClassName: "{{ . }}"
         {{- end }}
         resources:
           requests:
             storage: "{{ .Values.mysql.storage.size }}"
-
+{{- end }}
+{{- else if eq .Values.option.database "pgsql" }}
+{{- if not .Values.pgsql.useExternal }}
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: {{ include "devlake.fullname" . }}-pgsql
+  labels:
+    {{- include "devlake.labels" . | nindent 4 }}
+spec:
+  replicas: 1
+  serviceName: {{ include "devlake.fullname" . }}-pgsql
+  selector:
+    matchLabels:
+      {{- include "devlake.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      labels:
+        {{- include "devlake.selectorLabels" . | nindent 8 }}
+        devlakeComponent: pgsql
+    spec:
+      containers:
+        - name: pgsql
+          image: "{{ .Values.pgsql.image.repository }}:{{ .Values.pgsql.image.tag }}"
+          imagePullPolicy: {{ .Values.pgsql.image.pullPolicy }}
+          ports:
+            - name: pgsql
+              containerPort: 5432
+              protocol: TCP
+          livenessProbe:
+            exec:
+              command:
+                - "sh"
+                - "-c"
+                - "pg_isready -U postgres -h 127.0.0.1 -p 5432"
+            initialDelaySeconds: 60
+            timeoutSeconds: 30
+          readinessProbe:
+            exec:
+              command:
+                - "sh"
+                - "-c"
+                - "pg_isready -U postgres -h 127.0.0.1 -p 5432"
+            initialDelaySeconds: 5
+            timeoutSeconds: 10
+          {{- with .Values.pgsql.resources }}
+          resources:
+            {{- toYaml . | nindent 12 }}
+          {{- end }}
+          envFrom:
+            - configMapRef:
+                name: {{ include "devlake.fullname" . }}-config
+          volumeMounts:
+            - mountPath: /var/lib/postgresql
+              name: {{ include "devlake.fullname" . }}-pgsql-data
+            {{- if ne .Values.option.localtime "" }}
+            - name: {{ include "devlake.fullname" . }}-pgsql-localtime
+              mountPath: /etc/localtime
+              readOnly: true
+            {{- end }}
+      {{- with .Values.pgsql.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.pgsql.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.pgsql.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      volumes:
+        {{- if ne .Values.option.localtime "" }}
+        - name: {{ include "devlake.fullname" . }}-pgsql-localtime
+          hostPath:
+            path: {{ .Values.option.localtime }}
+            type: File
+        {{- end }}
+  volumeClaimTemplates:
+    - metadata:
+        name: {{ include "devlake.fullname" . }}-pgsql-data
+      spec:
+        accessModes: ["ReadWriteOnce"]
+        {{- with .Values.pgsql.storage.class }}
+        storageClassName: "{{ . }}"
+        {{- end }}
+        resources:
+          requests:
+            storage: "{{ .Values.pgsql.storage.size }}"
+{{- end }}
+{{- end }}
 
 ---
 # devlake
@@ -128,16 +219,7 @@ spec:
         devlakeComponent: lake
     spec:
       initContainers:
-        - name: waiting-mysql-ready
-          image: "{{ .Values.alpine.image.repository }}:{{ .Values.alpine.image.tag }}"
-          imagePullPolicy: {{ .Values.alpine.image.pullPolicy }}
-          command:
-            - 'sh'
-            - '-c'
-            - |
-              until nc -z -w 2 {{ include "devlake.fullname" . }}-mysql 3306 && echo mysql is ready ; do
-                sleep 2
-              done
+        {{- include "common.initContainerWaitDatabase" . | nindent 8 }}
       containers:
         - name: lake
           image: "{{ .Values.lake.image.repository }}:{{ .Values.lake.image.tag }}"
@@ -156,7 +238,7 @@ spec:
                 name: {{ include "devlake.fullname" . }}-config
           env:
             - name: DB_URL
-              value: mysql://{{ .Values.mysql.username }}:{{ .Values.mysql.password }}@{{ include "devlake.fullname" . }}-mysql:3306/{{ .Values.mysql.database }}?charset=utf8mb4&parseTime=True
+              value: "{{ include "database.url" . }}"
             - name: ENV_PATH
               value: /app/config/.env
           volumeMounts:
@@ -178,7 +260,7 @@ spec:
     - metadata:
         name: {{ include "devlake.fullname" . }}-lake-config
       spec:
-        accessModes: [ "ReadWriteOnce" ]
+        accessModes: ["ReadWriteOnce"]
         {{- with .Values.lake.storage.class }}
         storageClassName: "{{ . }}"
         {{- end }}
diff --git a/charts/devlake/values.yaml b/charts/devlake/values.yaml
index c8c91cf..50e2cb7 100644
--- a/charts/devlake/values.yaml
+++ b/charts/devlake/values.yaml
@@ -52,7 +52,7 @@ mysql:
     repository: mysql
     tag: 8.0.30
     pullPolicy: IfNotPresent
-  
+
   # resources config for mysql if have
   resources: {}
 
@@ -65,6 +65,49 @@ mysql:
   # affinity config for mysql if have
   affinity: {}
 
+pgsql:
+  # if use external pgsql server, please set true
+  #   by default using false, chart will create a single pgsql instance
+  useExternal: false
+
+  # the external pgsql server address
+  externalServer: 127.0.0.1
+
+  # external pgsql port
+  externalPort: 5432
+
+  # the username for devlake database
+  username: merico
+
+  # the password for devlake database
+  password: merico
+
+  # the database for devlake
+  database: lake
+
+  # storage for pgsql
+  storage:
+    # the storage class for pv, leave empty will using default
+    class: ""
+    size: 5Gi
+
+  # image for pgsql
+  image:
+    repository: postgres
+    tag: 14.5
+    pullPolicy: IfNotPresent
+
+  # resources config for pgsql if have
+  resources: {}
+
+  # nodeSelector config for pgsql if have
+  nodeSelector: {}
+
+  # tolerations config for pgsql if have
+  tolerations: []
+
+  # affinity config for pgsql if have
+  affinity: {}
 
 grafana:
   # image for grafana
@@ -72,7 +115,7 @@ grafana:
     repository: apache/devlake-dashboard
     tag: v0.14.2
     pullPolicy: Always
-  
+
   resources: {}
 
   nodeSelector: {}
@@ -132,7 +175,7 @@ alpine:
 service:
   # service type: NodePort/ClusterIP
   type: NodePort
-  # service port for devlake-ui
+  # node port for devlake-ui if NodePort is enabled
   uiPort: 32001
   grafanaPort : 32002
 
@@ -159,3 +202,5 @@ option:
   # localtime zone info from host path.
   localtime: /etc/localtime
 
+  # database type, supported: [mysql, pgsql]
+  database: mysql