diff --git a/kubernetes/Dockerfile b/kubernetes/Dockerfile index 4843094c..04993773 100644 --- a/kubernetes/Dockerfile +++ b/kubernetes/Dockerfile @@ -15,8 +15,14 @@ RUN export DEBIAN_FRONTEND=noninteractive \ && pip3 install setuptools \ && pip3 install 'git+https://github.com/zalando/patroni.git#egg=patroni[kubernetes]' \ - && mkdir -p /home/postgres \ - && chown postgres:postgres /home/postgres \ + && PGHOME=/home/postgres \ + && mkdir -p $PGHOME \ + && chown postgres $PGHOME \ + && sed -i "s|/var/lib/postgresql.*|$PGHOME:/bin/bash|" /etc/passwd \ + + # Set permissions for OpenShift + && chmod 775 $PGHOME \ + && chmod 664 /etc/passwd \ # Clean up && apt-get remove -y git python3-pip python3-wheel \ @@ -24,10 +30,10 @@ RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* /root/.cache -ADD entrypoint.sh callback.py / +ADD entrypoint.sh / EXPOSE 5432 8008 ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 USER postgres WORKDIR /home/postgres -CMD ["/bin/bash", "/entrypoint.sh"] +CMD ["/bin/bash", "/entrypoint.sh"] \ No newline at end of file diff --git a/kubernetes/callback.py b/kubernetes/callback.py deleted file mode 100755 index c3293149..00000000 --- a/kubernetes/callback.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python - -import logging -import os -import socket -import sys -import time - -from kubernetes import client as k8s_client, config as k8s_config -from urllib3.exceptions import HTTPError -from six.moves.http_client import HTTPException - -logger = logging.getLogger(__name__) - - -class CoreV1Api(k8s_client.CoreV1Api): - - def retry(func): - def wrapped(*args, **kwargs): - count = 0 - while True: - try: - return func(*args, **kwargs) - except (HTTPException, HTTPError, socket.error, socket.timeout): - if count >= 10: - raise - logger.info('Throttling API requests...') - time.sleep(2 ** count * 0.5) - count += 1 - return wrapped - - @retry - def patch_namespaced_endpoints(self, *args, **kwargs): - return super(CoreV1Api, self).patch_namespaced_endpoints(*args, **kwargs) - - -def patch_master_endpoint(api, namespace, cluster): - addresses = [k8s_client.V1EndpointAddress(ip=os.environ['POD_IP'])] - ports = [k8s_client.V1EndpointPort(port=5432)] - subsets = [k8s_client.V1EndpointSubset(addresses=addresses, ports=ports)] - body = k8s_client.V1Endpoints(subsets=subsets) - return api.patch_namespaced_endpoints(cluster, namespace, body) - - -def main(): - logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO) - if len(sys.argv) != 4 or sys.argv[1] not in ('on_start', 'on_stop', 'on_role_change'): - sys.exit('Usage: %s ', sys.argv[0]) - - action, role, cluster = sys.argv[1:4] - - k8s_config.load_incluster_config() - k8s_api = CoreV1Api() - - namespace = os.environ['KUBERNETES_NAMESPACE'] - - if role == 'master' and action in ('on_start', 'on_role_change'): - patch_master_endpoint(k8s_api, namespace, cluster) - - -if __name__ == '__main__': - main() diff --git a/kubernetes/entrypoint.sh b/kubernetes/entrypoint.sh index 39490946..d023689d 100755 --- a/kubernetes/entrypoint.sh +++ b/kubernetes/entrypoint.sh @@ -1,5 +1,12 @@ #!/bin/bash +if [[ $UID -ge 10000 ]]; then + GID=$(id -g) + sed -e "s/^postgres:x:[^:]*:[^:]*:/postgres:x:$UID:$GID:/" /etc/passwd > /tmp/passwd + cat /tmp/passwd > /etc/passwd + rm /tmp/passwd +fi + cat > /home/postgres/patroni.yml <<__EOF__ bootstrap: dcs: @@ -23,14 +30,10 @@ postgresql: password: '${PATRONI_SUPERUSER_PASSWORD}' replication: password: '${PATRONI_REPLICATION_PASSWORD}' - callbacks: - on_start: /callback.py - on_stop: /callback.py - on_role_change: /callback.py __EOF__ unset PATRONI_SUPERUSER_PASSWORD PATRONI_REPLICATION_PASSWORD export KUBERNETES_NAMESPACE=$PATRONI_KUBERNETES_NAMESPACE export POD_NAME=$PATRONI_NAME -exec /usr/bin/python3 /usr/local/bin/patroni /home/postgres/patroni.yml +exec /usr/bin/python3 /usr/local/bin/patroni /home/postgres/patroni.yml \ No newline at end of file diff --git a/kubernetes/openshift-example/README.md b/kubernetes/openshift-example/README.md new file mode 100644 index 00000000..a5424b00 --- /dev/null +++ b/kubernetes/openshift-example/README.md @@ -0,0 +1,49 @@ +# Patroni OpenShift Configuration +Patroni can be run in OpenShift. Based on the kubernetes configuration, the Dockerfile and Entrypoint has been modified to support the dynamic UID/GID configuration that is applied in OpenShift. This can be run under the standard `restricted` SCC. + +# Examples + +## Create test project + +``` +oc new-project patroni-test +``` + +## Build the image + +Note: Update the references when merged upstream. +Note: If deploying as a template for multiple users, the following commands should be performed in a shared namespace like `openshift`. + +``` +oc import-image postgres:10 --confirm -n openshift +oc new-build https://github.com/zalando/patroni --context-dir=kubernetes -n openshift +``` + +## Deploy the Image +Two configuration templates exist in [templates](templates) directory: +- Patroni Ephemeral +- Patroni Persistent + +The only difference is whether or not the statefulset requests persistent storage. + +## Create the Template +Install the template into the `openshift` namespace if this should be shared across projects: + +``` +oc create -f templates/template_patroni_ephemeral.yml -n openshift +``` + +Then, from your own project: + +``` +oc new-app patroni-pgsql-ephemeral +``` + +Once the pods are running, two configmaps should be available: + +``` +$ oc get configmap +NAME DATA AGE +patroniocp-config 0 1m +patroniocp-leader 0 1m +``` \ No newline at end of file diff --git a/kubernetes/openshift-example/templates/template_patroni_ephemeral.yml b/kubernetes/openshift-example/templates/template_patroni_ephemeral.yml new file mode 100644 index 00000000..1b68ec62 --- /dev/null +++ b/kubernetes/openshift-example/templates/template_patroni_ephemeral.yml @@ -0,0 +1,287 @@ +apiVersion: v1 +kind: Template +metadata: + name: patroni-pgsql-ephemeral + annotations: + description: |- + Patroni Postgresql database cluster, without persistent storage. + + WARNING: Any data stored will be lost upon pod destruction. Only use this template for testing. + iconClass: icon-postgresql + openshift.io/display-name: Patroni Postgresql (Ephemeral) + openshift.io/long-description: This template deploys a a patroni postgresql HA cluster without persistent storage. + tags: postgresql +objects: +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_CLUSTER_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_MASTER_SERVICE_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + role: master + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Secret + metadata: + name: ${PATRONI_CLUSTER_NAME} + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + stringData: + superuser-password: ${PATRONI_SUPERUSER_PASSWORD} + replication-password: ${PATRONI_REPLICATION_PASSWORD} +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_REPLICA_SERVICE_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + role: replica + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: apps/v1 + kind: StatefulSet + metadata: + creationTimestamp: null + generation: 3 + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${APPLICATION_NAME} + spec: + podManagementPolicy: OrderedReady + replicas: 3 + revisionHistoryLimit: 10 + selector: + matchLabels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + serviceName: ${APPLICATION_NAME} + template: + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + spec: + containers: + - env: + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: PATRONI_KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: PATRONI_KUBERNETES_LABELS + value: '{application: ${APPLICATION_NAME}, cluster-name: ${PATRONI_CLUSTER_NAME}}' + - name: PATRONI_SUPERUSER_USERNAME + value: ${PATRONI_SUPERUSER_USERNAME} + - name: PATRONI_SUPERUSER_PASSWORD + valueFrom: + secretKeyRef: + key: superuser-password + name: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_REPLICATION_USERNAME + value: ${PATRONI_REPLICATION_USERNAME} + - name: PATRONI_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + key: replication-password + name: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_SCOPE + value: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: PATRONI_POSTGRESQL_DATA_DIR + value: /home/postgres/pgdata/pgroot/data + - name: PATRONI_POSTGRESQL_PGPASS + value: /tmp/pgpass + - name: PATRONI_POSTGRESQL_LISTEN + value: 0.0.0.0:5432 + - name: PATRONI_RESTAPI_LISTEN + value: 0.0.0.0:8008 + image: docker-registry.default.svc:5000/${NAMESPACE}/patroni:latest + imagePullPolicy: IfNotPresent + name: ${APPLICATION_NAME} + ports: + - containerPort: 8008 + protocol: TCP + - containerPort: 5432 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/postgres/pgdata + name: pgdata + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: ${SERVICE_ACCOUNT} + serviceAccountName: ${SERVICE_ACCOUNT} + terminationGracePeriodSeconds: 0 + volumes: + - name: pgdata + emptyDir: {} + updateStrategy: + type: OnDelete +- apiVersion: v1 + kind: Endpoints + metadata: + name: ${APPLICATION_NAME} + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + subsets: [] +- apiVersion: v1 + kind: ServiceAccount + metadata: + name: ${SERVICE_ACCOUNT} +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: ${SERVICE_ACCOUNT} + rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - get + - list + - patch + - update + - watch + # delete is required only for 'patronictl remove' + - delete + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - patch + - update + # the following three privileges are necessary only when using endpoints + - create + - list + - watch + # delete is required only for for 'patronictl remove' + - delete + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - patch + - update + - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ${SERVICE_ACCOUNT} + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ${SERVICE_ACCOUNT} + subjects: + - kind: ServiceAccount + name: ${SERVICE_ACCOUNT} +parameters: +- description: The name of the application for labelling all artifacts. + displayName: Application Name + name: APPLICATION_NAME + value: patroni-ephemeral +- description: The name of the patroni-pgsql cluster. + displayName: Cluster Name + name: PATRONI_CLUSTER_NAME + value: patroni-ephemeral +- description: The name of the OpenShift Service exposed for the patroni-ephemeral-master container. + displayName: Master service name. + name: PATRONI_MASTER_SERVICE_NAME + value: patroni-ephemeral-master +- description: The name of the OpenShift Service exposed for the patroni-ephemeral-replica containers. + displayName: Replica service name. + name: PATRONI_REPLICA_SERVICE_NAME + value: patroni-ephemeral-replica +- description: Maximum amount of memory the container can use. + displayName: Memory Limit + name: MEMORY_LIMIT + value: 512Mi +- description: The OpenShift Namespace where the patroni and postgresql ImageStream resides. + displayName: ImageStream Namespace + name: NAMESPACE + value: openshift +- description: Username of the superuser account for initialization. + displayName: Superuser Username + name: PATRONI_SUPERUSER_USERNAME + value: postgres +- description: Password of the superuser account for initialization. + displayName: Superuser Passsword + name: PATRONI_SUPERUSER_PASSWORD + value: postgres +- description: Username of the replication account for initialization. + displayName: Replication Username + name: PATRONI_REPLICATION_USERNAME + value: postgres +- description: Password of the replication account for initialization. + displayName: Repication Passsword + name: PATRONI_REPLICATION_PASSWORD + value: postgres +- description: Service account name used for pods and rolebindings to form a cluster in the project. + displayName: Service Account + name: SERVICE_ACCOUNT + value: patroniocp diff --git a/kubernetes/openshift-example/templates/template_patroni_persistent.yaml b/kubernetes/openshift-example/templates/template_patroni_persistent.yaml new file mode 100644 index 00000000..46252aa5 --- /dev/null +++ b/kubernetes/openshift-example/templates/template_patroni_persistent.yaml @@ -0,0 +1,303 @@ +apiVersion: v1 +kind: Template +metadata: + name: patroni-pgsql-persistent + annotations: + description: |- + Patroni Postgresql database cluster, with persistent storage. + + WARNING: Any data stored will be lost upon pod destruction. Only use this template for testing. + iconClass: icon-postgresql + openshift.io/display-name: Patroni Postgresql (Persistent) + openshift.io/long-description: This template deploys a a patroni postgresql HA cluster without persistent storage. + tags: postgresql +objects: +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_CLUSTER_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_MASTER_SERVICE_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + role: master + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Secret + metadata: + name: ${PATRONI_CLUSTER_NAME} + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + stringData: + superuser-password: ${PATRONI_SUPERUSER_PASSWORD} + replication-password: ${PATRONI_REPLICATION_PASSWORD} +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${PATRONI_REPLICA_SERVICE_NAME} + spec: + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + role: replica + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: apps/v1 + kind: StatefulSet + metadata: + creationTimestamp: null + generation: 3 + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + name: ${APPLICATION_NAME} + spec: + podManagementPolicy: OrderedReady + replicas: 3 + revisionHistoryLimit: 10 + selector: + matchLabels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + serviceName: ${APPLICATION_NAME} + template: + metadata: + creationTimestamp: null + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + spec: + containers: + - env: + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: PATRONI_KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: PATRONI_KUBERNETES_LABELS + value: '{application: ${APPLICATION_NAME}, cluster-name: ${PATRONI_CLUSTER_NAME}}' + - name: PATRONI_SUPERUSER_USERNAME + value: ${PATRONI_SUPERUSER_USERNAME} + - name: PATRONI_SUPERUSER_PASSWORD + valueFrom: + secretKeyRef: + key: superuser-password + name: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_REPLICATION_USERNAME + value: ${PATRONI_REPLICATION_USERNAME} + - name: PATRONI_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + key: replication-password + name: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_SCOPE + value: ${PATRONI_CLUSTER_NAME} + - name: PATRONI_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: PATRONI_POSTGRESQL_DATA_DIR + value: /home/postgres/pgdata/pgroot/data + - name: PATRONI_POSTGRESQL_PGPASS + value: /tmp/pgpass + - name: PATRONI_POSTGRESQL_LISTEN + value: 0.0.0.0:5432 + - name: PATRONI_RESTAPI_LISTEN + value: 0.0.0.0:8008 + image: docker-registry.default.svc:5000/${NAMESPACE}/patroni:latest + imagePullPolicy: IfNotPresent + name: ${APPLICATION_NAME} + ports: + - containerPort: 8008 + protocol: TCP + - containerPort: 5432 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /home/postgres/pgdata + name: ${APPLICATION_NAME} + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: ${SERVICE_ACCOUNT} + serviceAccountName: ${SERVICE_ACCOUNT} + terminationGracePeriodSeconds: 0 + volumes: + - name: ${APPLICATION_NAME} + persistentVolumeClaim: + claimName: ${APPLICATION_NAME} + volumeClaimTemplates: + - metadata: + labels: + application: ${APPLICATION_NAME} + name: ${APPLICATION_NAME} + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: ${PVC_SIZE} + updateStrategy: + type: OnDelete +- apiVersion: v1 + kind: Endpoints + metadata: + name: ${APPLICATION_NAME} + labels: + application: ${APPLICATION_NAME} + cluster-name: ${PATRONI_CLUSTER_NAME} + subsets: [] +- apiVersion: v1 + kind: ServiceAccount + metadata: + name: ${SERVICE_ACCOUNT} +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: ${SERVICE_ACCOUNT} + rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - get + - list + - patch + - update + - watch + # delete is required only for 'patronictl remove' + - delete + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + - patch + - update + # the following three privileges are necessary only when using endpoints + - create + - list + - watch + # delete is required only for for 'patronictl remove' + - delete + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - patch + - update + - watch +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ${SERVICE_ACCOUNT} + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ${SERVICE_ACCOUNT} + subjects: + - kind: ServiceAccount + name: ${SERVICE_ACCOUNT} +parameters: +- description: The name of the application for labelling all artifacts. + displayName: Application Name + name: APPLICATION_NAME + value: patroni-persistent +- description: The name of the patroni-pgsql cluster. + displayName: Cluster Name + name: PATRONI_CLUSTER_NAME + value: patroni-persistent +- description: The name of the OpenShift Service exposed for the patroni-persistent-master container. + displayName: Master service name. + name: PATRONI_MASTER_SERVICE_NAME + value: patroni-persistent-master +- description: The name of the OpenShift Service exposed for the patroni-persistent-replica containers. + displayName: Replica service name. + name: PATRONI_REPLICA_SERVICE_NAME + value: patroni-persistent-replica +- description: Maximum amount of memory the container can use. + displayName: Memory Limit + name: MEMORY_LIMIT + value: 512Mi +- description: The OpenShift Namespace where the patroni and postgresql ImageStream resides. + displayName: ImageStream Namespace + name: NAMESPACE + value: openshift +- description: Username of the superuser account for initialization. + displayName: Superuser Username + name: PATRONI_SUPERUSER_USERNAME + value: postgres +- description: Password of the superuser account for initialization. + displayName: Superuser Passsword + name: PATRONI_SUPERUSER_PASSWORD + value: postgres +- description: Username of the replication account for initialization. + displayName: Replication Username + name: PATRONI_REPLICATION_USERNAME + value: postgres +- description: Password of the replication account for initialization. + displayName: Repication Passsword + name: PATRONI_REPLICATION_PASSWORD + value: postgres +- description: Service account name used for pods and rolebindings to form a cluster in the project. + displayName: Service Account + name: SERVICE_ACCOUNT + value: patroni-persistent +- description: The size of the persistent volume to create. + displayName: Persistent Volume Size + name: PVC_SIZE + value: 5Gi \ No newline at end of file diff --git a/kubernetes/openshift-example/test/Jenkinsfile b/kubernetes/openshift-example/test/Jenkinsfile new file mode 100644 index 00000000..aaddf8b0 --- /dev/null +++ b/kubernetes/openshift-example/test/Jenkinsfile @@ -0,0 +1,43 @@ +pipeline { + agent any + stages { + stage ('Deploy test pod'){ + when { + expression { + openshift.withCluster() { + openshift.withProject() { + return !openshift.selector( "dc", "pgbench" ).exists() + } + } + } + } + steps { + script { + openshift.withCluster() { + openshift.withProject() { + def pgbench = openshift.newApp( "https://github.com/stewartshea/docker-pgbench/", "--name=pgbench", "-e PGPASSWORD=postgres", "-e PGUSER=postgres", "-e PGHOST=patroni-persistent-master", "-e PGDATABASE=postgres", "-e TEST_CLIENT_COUNT=20", "-e TEST_DURATION=120" ) + def pgbenchdc = openshift.selector( "dc", "pgbench" ) + timeout(5) { + pgbenchdc.rollout().status() + } + } + } + } + } + } + stage ('Run benchmark Test'){ + steps { + sh ''' + oc exec $(oc get pods -l app=pgbench | grep Running | awk '{print $1}') ./test.sh + ''' + } + } + stage ('Clean up pgtest pod'){ + steps { + sh ''' + oc delete all -l app=pgbench + ''' + } + } + } +} diff --git a/kubernetes/openshift-example/test/README.md b/kubernetes/openshift-example/test/README.md new file mode 100644 index 00000000..f0f21430 --- /dev/null +++ b/kubernetes/openshift-example/test/README.md @@ -0,0 +1,2 @@ +# Jenkins Test +This pipeline test will create a separate deployment config for a pgbench pod and execute a test against the patroni cluster. This is a sample and should be customized. \ No newline at end of file diff --git a/kubernetes/patroni_k8s.yaml b/kubernetes/patroni_k8s.yaml index 3fcadeed..fd0e4926 100644 --- a/kubernetes/patroni_k8s.yaml +++ b/kubernetes/patroni_k8s.yaml @@ -36,6 +36,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: PATRONI_KUBERNETES_USE_ENDPOINTS + value: 'true' - name: PATRONI_KUBERNETES_LABELS value: '{application: patroni, cluster-name: patronidemo}' - name: PATRONI_SUPERUSER_USERNAME