Repository: linkerd/linkerd2
Pull request #4167 (2020-03-12 21:14:15)

Add KinD option to `install-pr` script
## Motivation

After #4147 added the `install-pr` script, installing PRs into existing
clusters does not work if that cluster is a KinD cluster

Changing the script to be able to use KinD, and specifically automate `kind
load` would be helpful!

## Solution

The script can now be used in the following ways.

```
❯ bin/install-pr --help
Install Linkerd with the changes made in a GitHub Pull Request.

Usage:
    --context: The name of the kubeconfig context to use

    # Install Linkerd into the current cluster
    bin/install-pr 1234

    # Install Linkerd into the current KinD cluster
    bin/install-pr [-k|--kind] 1234

    # Install Linkerd into the 'kind-pr-1234' KinD cluster
    bin/install-pr [-k|--kind] --context kind-pr-1234 1234
```

The script assumes that the cluster (KinD or not) has already been created. If
the cluster is a KinD cluster, the `-k|--kind` flag should be passed.

If the `--context` flag is not passsed, the install defaults to the current
context (`kubectl config current-context`).

I also added a [`-h|--help]` option that describes how to use the script.


kleimkuhler: As an example of the current error messaging, here is an example of `linkerd check` failing on a KinD cluster that just needed a little more time to reach a good state:

```
❯ bin/install-pr --kind 4155
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  336M    0  336M    0     0  5844k      0 --:--:--  0:00:59 --:--:-- 8350k

..

..

× no unschedulable pods
    linkerd-controller-f78f69cf4-rdkbl: 0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
    linkerd-identity-5fdfbcf98d-b6hnf: 0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.
    see https://linkerd.io/checks/#l5d-existence-unschedulable-pods for hints

Status check results are ×

Linkerd CLI installed!
Usage:
    bin/linkerd --context version

'linkerd check' failed!
Check again before debugging:
    bin/linkerd --context kind-pr-4155 check --pre
    bin/linkerd --context kind-pr-4155 check
    kubectl --context kind-pr-4155 -n linkerd get pods
```

It explains the Linkerd CLI was successfully installed, but `linkerd check` failed. As suggested, rerunning `bin/linkerd --context kind-pr-4155 check` did succeed the second time.
adleong: Now that we're building artifacts for all PRs, shouldn't this already work to install PRs from non-forked repos?  Or what change here is necessary to make that work?
kleimkuhler: @adleong Yes sorry that was taken care of in #4159. That should already work.

Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index beaba5c4f..163f2200d 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,25 @@
 CHANGES.md @wmorgan @klingerf @olix0r @admc
 GOVERNANCE.md @klingerf @olix0r
 MAINTAINERS.md @klingerf @olix0r
+
+/.github/workflows/ @alpeb @kleimkuhler
+/charts @alpeb @zaharidichev
+
+/charts/linkerd2-cni @hemakl @zaharidichev
+/charts/linkerd-service-mirror @zaharidichev
+
+/cli @alpeb @adleong @zaharidichev
+
+/cni-plugin @hemakl @zaharidichev
+
+/controller @adleong @alpeb @kleimkuhler
+
+/pkg @adleong @alpeb
+
+# These do not have clear owners today
+# /grafana
+# /proto
+# /proxy-identity
+# /test
+# /testutil
+# /web
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 275d5f78a..a161e325d 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -2,9 +2,14 @@ The Linkerd2 maintainers are:
 
 * Oliver Gould <ver@buoyant.io> @olix0r (super-maintainer)
 * Kevin Lingerfelt <kl@buoyant.io> @klingerf (super-maintainer)
-* Risha Mars <mars@buoyant.io> @rmars
-* Andrew Seigner <siggy@buoyant.io> @siggy
+* Alejandro Pedraza <alejandro@buoyant.io> @alpeb
 * Alex Leong <alex@buoyant.io> @adleong
+* Andrew Seigner <siggy@buoyant.io> @siggy
+* Eliza Weisman <eliza@buoyant.io> @hawkw
+* Hema Lee <Hemalekha.Lee@nordstrom.com> @hemakl
+* Kevin Leimkuhler <kevinl@buoyant.io> @kleimkuhler
+* Risha Mars <mars@buoyant.io> @rmars
+* Zahari Dichev <zahari@buoyant.io> @zaharidichev
 
 <!--
 # Adding a new maintainer
diff --git a/bin/install-pr b/bin/install-pr
index 40445c27c..c8c32cd72 100755
--- a/bin/install-pr
+++ b/bin/install-pr
@@ -9,63 +9,146 @@
 
 set -eo pipefail
 
+# Read script flags and arguments
+while :
+do
+  case $1 in
+    -h|--help)
+      echo "Install Linkerd with the changes made in a GitHub Pull Request."
+      echo ""
+      echo "Usage:"
+      echo "    --context: The name of the kubeconfig context to use"
+      echo ""
+      echo "    # Install Linkerd into the current cluster"
+      echo "    bin/install-pr 1234"
+      echo ""
+      echo "    # Install Linkerd into the current KinD cluster"
+      echo "    bin/install-pr [-k|--kind] 1234"
+      echo ""
+      echo "    # Install Linkerd into the 'kind-pr-1234' KinD cluster"
+      echo "    bin/install-pr [-k|--kind] --context kind-pr-1234 1234"
+      exit 0
+      ;;
+    --context)
+      context=$2
+      shift
+      ;;
+    -k|--kind)
+      is_kind=1
+      ;;
+    -?*)
+      echo "Error: Unknown option: $1" >&2
+      exit 1
+      ;;
+    *)
+      break
+  esac
+  shift
+done
+
 pr=$1
 
 if [ -z "$pr" ]
 then
-  echo "usage: ${0##*/} (PR number)" >&2
+  echo "Error: ${0##*/} accepts 1 argument" >&2
+  echo "Usage:" >&2
+  echo "    ${0##*/} ####" >&2
   exit 1
 fi
 
 if [ -z "$GITHUB_TOKEN" ]
 then
   # shellcheck disable=SC2016
-  echo 'Generate a personal access token at https://github.com/settings/tokens and set it in the $GITHUB_TOKEN env var'
+  echo 'Error: Generate a personal access token at https://github.com/settings/tokens and set it in the $GITHUB_TOKEN env var'
   exit 1
 fi
 
-auth="Authorization: token $GITHUB_TOKEN"
-
-branch=$(curl -s -H "$auth" "https://api.github.com/repos/linkerd/linkerd2/pulls/$pr" | jq -r '.head.ref')
+linkerd2_pulls_url="https://api.github.com/repos/linkerd/linkerd2/pulls"
+linkerd2_kind_integration_url="https://api.github.com/repos/linkerd/linkerd2/actions/workflows/kind_integration.yml"
 
-artifacts=$(curl -s -H "$auth" "https://api.github.com/repos/linkerd/linkerd2/actions/workflows/kind_integration.yml/runs?branch=$branch" | jq -r '.workflow_runs[0].artifacts_url')
-
-archive_url=$(curl -s -H "$auth" "$artifacts" | jq -r '.artifacts[0].archive_download_url')
+# Get the URL for downloading the artifacts archive
+auth="Authorization: token $GITHUB_TOKEN"
+branch=$(curl -sL -H "$auth" "$linkerd2_pulls_url/$pr" | jq -r '.head.ref')
+artifacts=$(curl -sL -H "$auth" "$linkerd2_kind_integration_url/runs?branch=$branch" | jq -r '.workflow_runs[0].artifacts_url')
+archive=$(curl -sL -H "$auth" "$artifacts" | jq -r '.artifacts[0].archive_download_url')
 
+bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 dir=$(mktemp -d -t "linkerd-pr-$pr.XXXXXXXXXX")
 
 cd "$dir" || exit
 
 echo "### Downloading images ###"
 
-curl -L -o archive.zip -H "$auth" "$archive_url"
-
+curl -L -o archive.zip -H "$auth" "$archive"
 unzip -o archive.zip
 
-echo "### Pushing images ###"
+echo "### Loading images into Docker ###"
 
-for archive in cli-bin cni-plugin controller debug grafana proxy web
-do 
-  image=$(docker load -i "$archive.tar" | sed "s/Loaded image: //")
-  docker push "$image"
-  tag=$(echo "$image" | cut -f 2 -d ":")
-done
+image=$(docker load -i cli-bin.tar | sed 's/Loaded image: //')
+tag=$(echo "$image" | cut -f 2 -d ":")
+
+if [ $is_kind ]
+then
+  # KinD context strings are created by prepending `kind-` to the cluster name
+  if [ "$context" ]
+  then
+    name=$(echo "$context" | sed -n -E "s/(kind)-(.*)/\2/p")
+  fi
+
+  for image in cni-plugin controller debug grafana proxy web
+  do
+    "$bindir"/kind load image-archive ${name:+'--name' "$name"} $image.tar || touch load_fail &
+  done
+
+  # Wait for `kind load` background processes to complete; exit early if any
+  # job failed
+  wait < <(jobs -p)
+  if [ -f load_fail ]
+  then
+    echo "Loading docker images into KinD cluster failed."
+    exit 1
+  fi
+else
+  for image in cni-plugin controller debug grafana proxy web
+  do
+    image=$(docker load -i "$image.tar" | sed 's/Loaded image: //')
+    docker push "$image"
+  done
+fi
 
 cd -
 
 rm -rf "$dir"
 
-linkerd=$(bin/docker-pull-binaries "$tag" | head -n 1)
+case $(uname) in
+  Darwin)
+    platform="darwin"
+    ;;
+  Linux)
+    platform="linux"
+    ;;
+  *)
+    platform="windows"
+    ;;
+esac
+
+linkerd=$("$bindir"/docker-pull-binaries "$tag" | awk -v platform=$platform '$0 ~ platform')
 
 echo "### Pre checks ###"
 
-$linkerd check --pre
+(
+  set -x
+  "$linkerd" ${context:+'--context' "$context"} check --pre
+)
 
 echo "### Installing $tag ###"
 
-$linkerd install | kubectl apply -f -
-
-$linkerd check
+(
+  set -x
+  "$linkerd" ${context:+'--context' "$context"} install | kubectl ${context:+'--context' "$context"} apply -f -
+  "$linkerd" ${context:+'--context' "$context"} check
+)
 
-echo "### Linkerd installed. CLI available: "
+echo ""
+echo "Linkerd installed. CLI available:"
 echo "$linkerd"
diff --git a/controller/api/destination/watcher/endpoints_watcher.go b/controller/api/destination/watcher/endpoints_watcher.go
index a1ecdfe99..fd2f6b450 100644
--- a/controller/api/destination/watcher/endpoints_watcher.go
+++ b/controller/api/destination/watcher/endpoints_watcher.go
@@ -570,9 +570,9 @@ func (pp *portPublisher) unsubscribe(listener EndpointUpdateListener) {
 
 // getTargetPort returns the port specified as an argument if no service is
 // present. If the service is present and it has a port spec matching the
-// specified port and a target port configured, it returns the name of the
-// service's port (not the name of the target pod port), so that it can be
-// looked up in the endpoints API response, which uses service port names.
+// specified port, it returns the name of the service's port (not the name
+// of the target pod port), so that it can be looked up in the endpoints API
+// response, which uses service port names.
 func getTargetPort(service *corev1.Service, port Port) namedPort {
 	// Use the specified port as the target port by default
 	targetPort := intstr.FromInt(int(port))
@@ -581,10 +581,10 @@ func getTargetPort(service *corev1.Service, port Port) namedPort {
 		return targetPort
 	}
 
-	// If a port spec exists with a port matching the specified port and a target
-	// port configured, use that port spec's name as the target port
+	// If a port spec exists with a port matching the specified port use that
+	// port spec's name as the target port
 	for _, portSpec := range service.Spec.Ports {
-		if portSpec.Port == int32(port) && portSpec.TargetPort != intstr.FromInt(0) {
+		if portSpec.Port == int32(port) {
 			return intstr.FromString(portSpec.Name)
 		}
 	}
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index f8caca5bb..fa3a54c6b 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -600,9 +600,10 @@ func (hc *HealthChecker) allCategories() []category {
 					},
 				},
 				{
-					description: "control plane replica sets are ready",
-					hintAnchor:  "l5d-existence-replicasets",
-					fatal:       true,
+					description:   "control plane replica sets are ready",
+					hintAnchor:    "l5d-existence-replicasets",
+					retryDeadline: hc.RetryDeadline,
+					fatal:         true,
 					check: func(context.Context) error {
 						controlPlaneReplicaSet, err := hc.kubeAPI.GetReplicaSets(hc.ControlPlaneNamespace)
 						if err != nil {
@@ -612,9 +613,10 @@ func (hc *HealthChecker) allCategories() []category {
 					},
 				},
 				{
-					description: "no unschedulable pods",
-					hintAnchor:  "l5d-existence-unschedulable-pods",
-					fatal:       true,
+					description:   "no unschedulable pods",
+					hintAnchor:    "l5d-existence-unschedulable-pods",
+					retryDeadline: hc.RetryDeadline,
+					fatal:         true,
 					check: func(context.Context) error {
 						// do not save this into hc.controlPlanePods, as this check may
 						// succeed prior to all expected control plane pods being up


Test patch:
diff --git a/controller/api/destination/watcher/endpoints_watcher_test.go b/controller/api/destination/watcher/endpoints_watcher_test.go
index 07d566848..9e0e09b73 100644
--- a/controller/api/destination/watcher/endpoints_watcher_test.go
+++ b/controller/api/destination/watcher/endpoints_watcher_test.go
@@ -736,7 +736,7 @@ subsets:
 			},
 			serviceType: "mirrored service with remapped port in endpoints",
 			id:          ServiceID{Name: "name1-remote", Namespace: "ns"},
-			port:        9999,
+			port:        8989,
 			expectedAddresses: []string{
 				"172.17.0.12:9999/gateway-identity-1",
 			},