You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by ka...@apache.org on 2020/06/03 09:35:17 UTC

[beam] branch master updated: [BEAM-5863] Jenkins job to deploy Beam Metrics infrastructure automatically upon code changes (#11816)

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

kamilwu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 1875e24  [BEAM-5863] Jenkins job to deploy Beam Metrics infrastructure automatically upon code changes (#11816)
1875e24 is described below

commit 1875e24ea5571797415f7abbac7d6f9a11e1ba1a
Author: Kamil Wasilewski <ka...@polidea.com>
AuthorDate: Wed Jun 3 11:34:43 2020 +0200

    [BEAM-5863] Jenkins job to deploy Beam Metrics infrastructure automatically upon code changes (#11816)
---
 .test-infra/jenkins/CommonJobProperties.groovy     | 17 --------
 .test-infra/jenkins/Kubernetes.groovy              | 30 ++++++++++-----
 .test-infra/jenkins/README.md                      |  1 +
 .../job_PostCommit_BeamMetrics_Publish.groovy      | 40 +++++++++++++++++++
 .test-infra/metrics/build.gradle                   | 45 +++++++++++++++++++++-
 .../metrics/build_and_publish_containers.sh        | 22 +++--------
 ...beam-grafana-etcdata-persistentvolumeclaim.yaml |  3 +-
 ...beam-grafana-libdata-persistentvolumeclaim.yaml |  3 +-
 ...beam-grafana-logdata-persistentvolumeclaim.yaml |  3 +-
 .../{ => kubernetes}/beam-influxdb-autobackup.yaml |  2 +
 ...eam-influxdb-backups-persistentvolumeclaim.yaml |  2 +
 ...eam-influxdb-storage-persistentvolumeclaim.yaml |  2 +
 .../metrics/{ => kubernetes}/beam-influxdb.yaml    |  2 +-
 ...beam-postgresql-data-persistentvolumeclaim.yaml |  3 +-
 .../{ => kubernetes}/beamgrafana-deploy.yaml       | 10 ++---
 .test-infra/metrics/sync/github/Dockerfile         |  8 ++--
 .test-infra/metrics/sync/jenkins/Dockerfile        |  5 ++-
 .test-infra/metrics/sync/jira/Dockerfile           |  5 ++-
 18 files changed, 137 insertions(+), 66 deletions(-)

diff --git a/.test-infra/jenkins/CommonJobProperties.groovy b/.test-infra/jenkins/CommonJobProperties.groovy
index 519d561..df0d83a 100644
--- a/.test-infra/jenkins/CommonJobProperties.groovy
+++ b/.test-infra/jenkins/CommonJobProperties.groovy
@@ -242,23 +242,6 @@ class CommonJobProperties {
     return argList.join(' ')
   }
 
-  static def setupKubernetes(def context, def namespace, def kubeconfigLocation) {
-    context.steps {
-      shell('gcloud container clusters get-credentials io-datastores --zone=us-central1-a --verbosity=debug')
-      shell("cp /home/jenkins/.kube/config ${kubeconfigLocation}")
-
-      shell("kubectl --kubeconfig=${kubeconfigLocation} create namespace ${namespace}")
-      shell("kubectl --kubeconfig=${kubeconfigLocation} config set-context \$(kubectl config current-context) --namespace=${namespace}")
-    }
-  }
-
-  static def cleanupKubernetes(def context, def namespace, def kubeconfigLocation) {
-    context.steps {
-      shell("kubectl --kubeconfig=${kubeconfigLocation} delete namespace ${namespace}")
-      shell("rm ${kubeconfigLocation}")
-    }
-  }
-
   // Namespace must contain lower case alphanumeric characters or '-'
   static String getKubernetesNamespace(def jobName) {
     jobName = jobName.replaceAll("_", "-").toLowerCase()
diff --git a/.test-infra/jenkins/Kubernetes.groovy b/.test-infra/jenkins/Kubernetes.groovy
index 6e50383..6f8270a 100644
--- a/.test-infra/jenkins/Kubernetes.groovy
+++ b/.test-infra/jenkins/Kubernetes.groovy
@@ -22,16 +22,21 @@ class Kubernetes {
 
   private static final String KUBERNETES_SCRIPT = "${KUBERNETES_DIR}/kubernetes.sh"
 
+  private static final String DEFAULT_CLUSTER = 'io-datastores'
+
   private static def job
 
   private static String kubeconfigLocation
 
   private static String namespace
 
-  private Kubernetes(job, String kubeconfigLocation, String namespace) {
+  private static String cluster
+
+  private Kubernetes(job, String kubeconfigLocation, String namespace, String cluster) {
     this.job = job
     this.kubeconfigLocation = kubeconfigLocation
     this.namespace = namespace
+    this.cluster = cluster
   }
 
   /**
@@ -39,10 +44,12 @@ class Kubernetes {
    *
    * @param job - jenkins job
    * @param kubeconfigLocation - place where kubeconfig will be created
-   * @param namepsace - kubernetes namespace
+   * @param namespace - kubernetes namespace. If empty, the default namespace will be used
+   * @param cluster - name of the cluster to get credentials for
    */
-  static Kubernetes create(job, String kubeconfigLocation, String namespace) {
-    Kubernetes kubernetes = new Kubernetes(job, kubeconfigLocation, namespace)
+  static Kubernetes create(job, String kubeconfigLocation, String namespace = '',
+                           String cluster = DEFAULT_CLUSTER) {
+    Kubernetes kubernetes = new Kubernetes(job, kubeconfigLocation, namespace, cluster)
     setupKubeconfig()
     setupNamespace()
     addCleanupSteps()
@@ -55,14 +62,17 @@ class Kubernetes {
       environmentVariables {
         env('KUBECONFIG', kubeconfigLocation)
       }
+      shell("gcloud container clusters get-credentials ${cluster} --zone=us-central1-a")
     }
   }
 
   private static void setupNamespace() {
-    job.steps {
-      shell("${KUBERNETES_SCRIPT} createNamespace ${namespace}")
-      environmentVariables {
-        env('KUBERNETES_NAMESPACE', namespace)
+    if (!namespace.isEmpty()) {
+      job.steps {
+        shell("${KUBERNETES_SCRIPT} createNamespace ${namespace}")
+        environmentVariables {
+          env('KUBERNETES_NAMESPACE', namespace)
+        }
       }
     }
   }
@@ -71,7 +81,9 @@ class Kubernetes {
     job.publishers {
       postBuildScripts {
         steps {
-          shell("${KUBERNETES_SCRIPT} deleteNamespace ${namespace}")
+          if (!namespace.isEmpty()) {
+            shell("${KUBERNETES_SCRIPT} deleteNamespace ${namespace}")
+          }
           shell("rm ${kubeconfigLocation}")
         }
         onlyIfBuildSucceeds(false)
diff --git a/.test-infra/jenkins/README.md b/.test-infra/jenkins/README.md
index de03165..6723cc0 100644
--- a/.test-infra/jenkins/README.md
+++ b/.test-infra/jenkins/README.md
@@ -47,6 +47,7 @@ Beam Jenkins overview page: [link](https://builds.apache.org/view/A-D/view/Beam/
 
 | Name | Link | PR Trigger Phrase | Cron Status |
 |------|------|-------------------|-------------|
+| beam_PostCommit_BeamMetrics_Publish | [cron](https://builds.apache.org/job/beam_PostCommit_BeamMetrics_Publish/) | N/A | [![Build Status](https://builds.apache.org/job/beam_PostCommit_BeamMetrics_Publish/badge/icon)](https://builds.apache.org/job/beam_PostCommit_BeamMetrics_Publish) |
 | beam_PostCommit_CrossLanguageValidatesRunner | [cron](https://builds.apache.org/job/beam_PostCommit_XVR_Flink/), [phrase](https://builds.apache.org/job/beam_PostCommit_XVR_Flink_PR/) | `Run XVR_Flink PostCommit` | [![Build Status](https://builds.apache.org/job/beam_PostCommit_XVR_Flink/badge/icon)](https://builds.apache.org/job/beam_PostCommit_XVR_Flink) |
 | beam_PostCommit_Go | [cron](https://builds.apache.org/job/beam_PostCommit_Go/), [phrase](https://builds.apache.org/job/beam_PostCommit_Go_PR/) | `Run Go PostCommit` | [![Build Status](https://builds.apache.org/job/beam_PostCommit_Go/badge/icon)](https://builds.apache.org/job/beam_PostCommit_Go) |
 | beam_PostCommit_Go_VR_Flink | [cron](https://builds.apache.org/job/beam_PostCommit_Go_VR_Flink/), [phrase](https://builds.apache.org/job/beam_PostCommit_Go_VR_Flink_PR/) | `Run Go Flink ValidatesRunner` | [![Build Status](https://builds.apache.org/job/beam_PostCommit_Go_VR_Flink/badge/icon)](https://builds.apache.org/job/beam_PostCommit_Go_VR_Flink/) |
diff --git a/.test-infra/jenkins/job_PostCommit_BeamMetrics_Publish.groovy b/.test-infra/jenkins/job_PostCommit_BeamMetrics_Publish.groovy
new file mode 100644
index 0000000..629fd51
--- /dev/null
+++ b/.test-infra/jenkins/job_PostCommit_BeamMetrics_Publish.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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, eit    her express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import CommonJobProperties as commonJobProperties
+import Kubernetes
+import PostcommitJobBuilder
+
+PostcommitJobBuilder.postCommitJob('beam_PostCommit_BeamMetrics_Publish', '',
+  'Beam Metrics deployment', this) {
+
+  description('Applies new configuration to Beam Metrics infrastructure.')
+
+  // Set common parameters.
+  commonJobProperties.setTopLevelMainJobProperties(delegate)
+
+  Kubernetes.create(delegate, 'config-metrics', '', 'metrics')
+
+  steps {
+    gradle {
+      rootBuildScriptDir(commonJobProperties.checkoutDir)
+      tasks(':beam-test-infra-metrics:deploy')
+      commonJobProperties.setGradleSwitches(delegate)
+    }
+  }
+}
diff --git a/.test-infra/metrics/build.gradle b/.test-infra/metrics/build.gradle
index 9bd5a6d..5eebd6a 100644
--- a/.test-infra/metrics/build.gradle
+++ b/.test-infra/metrics/build.gradle
@@ -20,6 +20,7 @@ plugins {
   id 'org.apache.beam.module'
   // https://github.com/avast/gradle-docker-compose-plugin
   id 'com.avast.gradle.docker-compose'
+  id 'org.ajoberstar.grgit'
 }
 applyGroovyNature()
 
@@ -50,9 +51,49 @@ dockerCompose {
 
 dockerCompose.isRequiredBy(testMetricsStack)
 
-task preCommit { dependsOn testMetricsStack }
+task validateConfiguration(type: Exec) {
+  commandLine 'sh', '-c', 'kubectl apply --dry-run=true -Rf kubernetes'
+}
+
+task preCommit {
+  dependsOn validateConfiguration
+  dependsOn testMetricsStack
+}
+
+task buildAndPublishContainers(type: Exec) {
+  commandLine './build_and_publish_containers.sh', 'true'
+}
+
+// Applies new configuration to all resources labeled with `app=beammetrics`
+// and forces Kubernetes to re-pull images.
+task applyConfiguration() {
+  doLast {
+    assert grgit : 'Cannot use outside of git repository'
+
+    def git = grgit.open()
+    def commitedChanges = git.log(paths: ['.test-infra/metrics']).findAll {
+      it.dateTime > ZonedDateTime.now().minusHours(6)
+    }
+
+    if (!commitedChanges.isEmpty()) {
+      exec {
+        executable 'sh'
+        args '-c', 'kubectl apply --selector=app=beammetrics --prune=true -Rf kubernetes && \
+          kubectl wait --selector=app=beammetrics --for=condition=available deployment && \
+          kubectl get --selector=app=beammetrics -o name deployments | xargs -r kubectl rollout restart'
+      }
+    } else {
+      println 'No changes committed since the last 6 hours.'
+    }
+  }
+}
+
+task deploy {
+  dependsOn buildAndPublishContainers
+  dependsOn applyConfiguration
+}
 
-task checkProber(type:Test) {
+task checkProber(type: Test) {
   include "**/ProberTests.class"
 }
 
diff --git a/.test-infra/metrics/build_and_publish_containers.sh b/.test-infra/metrics/build_and_publish_containers.sh
index 53b3237..886008b 100755
--- a/.test-infra/metrics/build_and_publish_containers.sh
+++ b/.test-infra/metrics/build_and_publish_containers.sh
@@ -19,14 +19,11 @@
 #
 # This script will:
 # * build containers for beam metrics
-# * publish containers if second parameter is provided
-# * update beamgrafana-deploy.yaml with new container versions
+# * publish containers if first parameter is provided
+#
+# Usage:
+# "build_and_publish_container.sh [<should_publish_containers>] [<container_tag_name>]"
 #
-
-if [ "$#" = 0 ]; then
-  echo "build_and_publish_container.sh <container_tag_name> [<should_publish_containers>]"
-  exit 0;
-fi
 
 if [ -z "${PROJECT_ID}" ]; then
   echo "PROJECT_ID not set, trying to automatically get it from gcloud config."
@@ -36,8 +33,8 @@ fi
 
 echo
 echo ===========Start==========
-CONTAINER_VERSION_NAME=$1
-DO_PUSH=$2
+DO_PUSH=$1
+CONTAINER_VERSION_NAME=${2:-latest}
 
 echo
 echo ===========Building containers==========
@@ -54,10 +51,3 @@ if [ "$DO_PUSH" = true ]; then
   docker push gcr.io/${PROJECT_ID}/beammetricssyncjira:$CONTAINER_VERSION_NAME
   docker push gcr.io/${PROJECT_ID}/beammetricssyncgithub:$CONTAINER_VERSION_NAME
 fi
-
-echo
-echo ===========Updating deployment script==========
-set -o xtrace
-sed -i "s/\( *image: gcr.io\/${PROJECT_ID}\/beam.*:\).*$/\1$CONTAINER_VERSION_NAME/g" ./beamgrafana-deploy.yaml
-set +o xtrace
-
diff --git a/.test-infra/metrics/beam-grafana-etcdata-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-grafana-etcdata-persistentvolumeclaim.yaml
similarity index 96%
rename from .test-infra/metrics/beam-grafana-etcdata-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-grafana-etcdata-persistentvolumeclaim.yaml
index 1b85f3b..04fcac5 100644
--- a/.test-infra/metrics/beam-grafana-etcdata-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-grafana-etcdata-persistentvolumeclaim.yaml
@@ -19,9 +19,9 @@
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
-  creationTimestamp: null
   labels:
     io.kompose.service: beam-grafana-etcdata
+    app: beammetrics
   name: beam-grafana-etcdata
 spec:
   accessModes:
@@ -29,4 +29,3 @@ spec:
   resources:
     requests:
       storage: 100Mi
-status: {}
diff --git a/.test-infra/metrics/beam-grafana-libdata-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-grafana-libdata-persistentvolumeclaim.yaml
similarity index 96%
rename from .test-infra/metrics/beam-grafana-libdata-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-grafana-libdata-persistentvolumeclaim.yaml
index 8fbacb1..9f346b2 100644
--- a/.test-infra/metrics/beam-grafana-libdata-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-grafana-libdata-persistentvolumeclaim.yaml
@@ -17,9 +17,9 @@
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
-  creationTimestamp: null
   labels:
     io.kompose.service: beam-grafana-libdata
+    app: beammetrics
   name: beam-grafana-libdata
 spec:
   accessModes:
@@ -27,4 +27,3 @@ spec:
   resources:
     requests:
       storage: 100Mi
-status: {}
diff --git a/.test-infra/metrics/beam-grafana-logdata-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-grafana-logdata-persistentvolumeclaim.yaml
similarity index 96%
rename from .test-infra/metrics/beam-grafana-logdata-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-grafana-logdata-persistentvolumeclaim.yaml
index 5856e1a..c41bcfd 100644
--- a/.test-infra/metrics/beam-grafana-logdata-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-grafana-logdata-persistentvolumeclaim.yaml
@@ -19,9 +19,9 @@
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
-  creationTimestamp: null
   labels:
     io.kompose.service: beam-grafana-logdata
+    app: beammetrics
   name: beam-grafana-logdata
 spec:
   accessModes:
@@ -29,4 +29,3 @@ spec:
   resources:
     requests:
       storage: 100Mi
-status: {}
diff --git a/.test-infra/metrics/beam-influxdb-autobackup.yaml b/.test-infra/metrics/kubernetes/beam-influxdb-autobackup.yaml
similarity index 98%
rename from .test-infra/metrics/beam-influxdb-autobackup.yaml
rename to .test-infra/metrics/kubernetes/beam-influxdb-autobackup.yaml
index b513aac..0723165 100644
--- a/.test-infra/metrics/beam-influxdb-autobackup.yaml
+++ b/.test-infra/metrics/kubernetes/beam-influxdb-autobackup.yaml
@@ -20,6 +20,8 @@
 apiVersion: batch/v1beta1
 kind: CronJob
 metadata:
+  labels:
+    app: beammetrics
   name: influxdb-autobackup
 spec:
   schedule: "@weekly"
diff --git a/.test-infra/metrics/beam-influxdb-backups-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-influxdb-backups-persistentvolumeclaim.yaml
similarity index 97%
rename from .test-infra/metrics/beam-influxdb-backups-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-influxdb-backups-persistentvolumeclaim.yaml
index 493337c..fe6c194 100644
--- a/.test-infra/metrics/beam-influxdb-backups-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-influxdb-backups-persistentvolumeclaim.yaml
@@ -20,6 +20,8 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: influxdb-backups
+  labels:
+    app: beammetrics
 spec:
   accessModes:
   - ReadWriteOnce
diff --git a/.test-infra/metrics/beam-influxdb-storage-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-influxdb-storage-persistentvolumeclaim.yaml
similarity index 97%
rename from .test-infra/metrics/beam-influxdb-storage-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-influxdb-storage-persistentvolumeclaim.yaml
index 7b3a858..44fa692 100644
--- a/.test-infra/metrics/beam-influxdb-storage-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-influxdb-storage-persistentvolumeclaim.yaml
@@ -20,6 +20,8 @@ apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
   name: influxdb-storage
+  labels:
+    app: beammetrics
 spec:
   accessModes:
   - ReadWriteOnce
diff --git a/.test-infra/metrics/beam-influxdb.yaml b/.test-infra/metrics/kubernetes/beam-influxdb.yaml
similarity index 99%
rename from .test-infra/metrics/beam-influxdb.yaml
rename to .test-infra/metrics/kubernetes/beam-influxdb.yaml
index 6b261c0..32c9eb5 100644
--- a/.test-infra/metrics/beam-influxdb.yaml
+++ b/.test-infra/metrics/kubernetes/beam-influxdb.yaml
@@ -29,7 +29,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    app: influxdb
+    app: beammetrics
   name: influxdb
 spec:
   replicas: 1
diff --git a/.test-infra/metrics/beam-postgresql-data-persistentvolumeclaim.yaml b/.test-infra/metrics/kubernetes/beam-postgresql-data-persistentvolumeclaim.yaml
similarity index 96%
rename from .test-infra/metrics/beam-postgresql-data-persistentvolumeclaim.yaml
rename to .test-infra/metrics/kubernetes/beam-postgresql-data-persistentvolumeclaim.yaml
index 940b03f..3c6c8ba 100644
--- a/.test-infra/metrics/beam-postgresql-data-persistentvolumeclaim.yaml
+++ b/.test-infra/metrics/kubernetes/beam-postgresql-data-persistentvolumeclaim.yaml
@@ -19,9 +19,9 @@
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
-  creationTimestamp: null
   labels:
     io.kompose.service: beam-postgresql-data
+    app: beammetrics
   name: beam-postgresql-data
 spec:
   accessModes:
@@ -29,4 +29,3 @@ spec:
   resources:
     requests:
       storage: 100Mi
-status: {}
diff --git a/.test-infra/metrics/beamgrafana-deploy.yaml b/.test-infra/metrics/kubernetes/beamgrafana-deploy.yaml
similarity index 95%
rename from .test-infra/metrics/beamgrafana-deploy.yaml
rename to .test-infra/metrics/kubernetes/beamgrafana-deploy.yaml
index 7c5a83d..f548259 100644
--- a/.test-infra/metrics/beamgrafana-deploy.yaml
+++ b/.test-infra/metrics/kubernetes/beamgrafana-deploy.yaml
@@ -21,7 +21,7 @@ kind: Deployment
 metadata:
   name: beamgrafana
   labels:
-    app: beamgrafana
+    app: beammetrics
 spec:
   selector:
     matchLabels:
@@ -37,7 +37,7 @@ spec:
         fsGroup: 1000
       containers:
       - name: beamgrafana
-        image: gcr.io/apache-beam-testing/beamgrafana:beammetrics20200526
+        image: gcr.io/apache-beam-testing/beamgrafana
         securityContext:
           runAsUser: 0
         env:
@@ -104,7 +104,7 @@ spec:
             mountPath: /secrets/cloudsql
             readOnly: true
       - name: beammetricssyncjenkins
-        image: gcr.io/apache-beam-testing/beammetricssyncjenkins:beammetrics20200506
+        image: gcr.io/apache-beam-testing/beammetricssyncjenkins
         env:
           - name: DB_HOST
             value: 127.0.0.1
@@ -123,7 +123,7 @@ spec:
                 name: beammetrics-psql-db-credentials
                 key: password
       - name: beammetricssyncjira
-        image: gcr.io/apache-beam-testing/beammetricssyncjira:beammetrics20200506
+        image: gcr.io/apache-beam-testing/beammetricssyncjira
         env:
           - name: DB_HOST
             value: 127.0.0.1
@@ -142,7 +142,7 @@ spec:
                 name: beammetrics-psql-db-credentials
                 key: password
       - name: beammetricssyncgithub
-        image: gcr.io/apache-beam-testing/beammetricssyncgithub:beammetrics20200506
+        image: gcr.io/apache-beam-testing/beammetricssyncgithub
         env:
           - name: DB_HOST
             value: 127.0.0.1
diff --git a/.test-infra/metrics/sync/github/Dockerfile b/.test-infra/metrics/sync/github/Dockerfile
index 0ee43c5..1ecedc0 100644
--- a/.test-infra/metrics/sync/github/Dockerfile
+++ b/.test-infra/metrics/sync/github/Dockerfile
@@ -16,14 +16,14 @@
 # limitations under the License.
 ################################################################################
 
-FROM python:3
+FROM python:3-slim
 
 WORKDIR /usr/src/app
 
-COPY . .
+COPY requirements.txt .
 
-RUN pip install pylint yapf nose
-RUN pip install --no-cache-dir -r requirements.txt
+RUN pip install --no-cache-dir -r requirements.txt pylint yapf nose
 
+COPY . .
 
 CMD python ./sync.py
diff --git a/.test-infra/metrics/sync/jenkins/Dockerfile b/.test-infra/metrics/sync/jenkins/Dockerfile
index ac74829..167e5b8 100644
--- a/.test-infra/metrics/sync/jenkins/Dockerfile
+++ b/.test-infra/metrics/sync/jenkins/Dockerfile
@@ -16,13 +16,14 @@
 # limitations under the License.
 ################################################################################
 
-FROM python:3
+FROM python:3-slim
 
 WORKDIR /usr/src/app
 
-COPY . .
+COPY requirements.txt .
 
 RUN pip install --no-cache-dir -r requirements.txt
 
+COPY . .
 
 CMD python ./syncjenkins.py
diff --git a/.test-infra/metrics/sync/jira/Dockerfile b/.test-infra/metrics/sync/jira/Dockerfile
index afd681e..02264c6 100644
--- a/.test-infra/metrics/sync/jira/Dockerfile
+++ b/.test-infra/metrics/sync/jira/Dockerfile
@@ -16,13 +16,14 @@
 # limitations under the License.
 ################################################################################
 
-FROM python:3
+FROM python:3-slim
 
 WORKDIR /usr/src/app
 
-COPY . .
+COPY requirements.txt .
 
 RUN pip install --no-cache-dir -r requirements.txt
 
+COPY . .
 
 CMD python ./syncjira.py