diff --git a/Dockerfile-argoexec b/Dockerfile-argoexec index ab129a18f03b..f8d004fc18be 100644 --- a/Dockerfile-argoexec +++ b/Dockerfile-argoexec @@ -1,12 +1,34 @@ FROM debian:9.1 RUN apt-get update && \ - apt-get install -y curl jq procps git && \ + apt-get install -y curl jq procps git tar && \ rm -rf /var/lib/apt/lists/* && \ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ chmod +x ./kubectl && \ mv ./kubectl /bin/ +ENV DOCKER_CHANNEL edge +ENV DOCKER_VERSION 17.10.0-ce +# TODO ENV DOCKER_SHA256 +# https://github.com/docker/docker-ce/blob/5b073ee2cf564edee5adca05eee574142f7627bb/components/packaging/static/hash_files !! +# (no SHA file artifacts on download.docker.com yet as of 2017-06-07 though) + +RUN set -ex; \ + dockerArch='x86_64'; \ + if ! curl -fL -o docker.tgz "https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${dockerArch}/docker-${DOCKER_VERSION}.tgz"; then \ + echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${dockerArch}'"; \ + exit 1; \ + fi; \ + \ + tar --extract \ + --file docker.tgz \ + --strip-components 1 \ + --directory /usr/local/bin/ \ + ; \ + rm docker.tgz; \ + \ + dockerd -v; \ + docker -v COPY dist/argoexec /bin/ diff --git a/workflow/common/common.go b/workflow/common/common.go index 028c65009b6a..c177f0fc5763 100644 --- a/workflow/common/common.go +++ b/workflow/common/common.go @@ -32,6 +32,8 @@ const ( DockerLibVolumeName = "docker-lib" // DockerLibHostPath is the host directory path containing docker runtime state DockerLibHostPath = "/var/lib/docker" + // DockerSockVolumeName is the volume name for the /var/run/docker.sock host path volume + DockerSockVolumeName = "docker-sock" // AnnotationKeyNodeName is the pod metadata annotation key containing the workflow node name AnnotationKeyNodeName = wfv1.CRDFullName + "/node-name" diff --git a/workflow/controller/workflowpod.go b/workflow/controller/workflowpod.go index 4a0004fe8514..11eaa8e2e5b9 100644 --- a/workflow/controller/workflowpod.go +++ b/workflow/controller/workflowpod.go @@ -41,10 +41,12 @@ var ( Name: volumePodMetadata.Name, MountPath: common.PodMetadataMountPath, } - // volumeDockerLib provides the argoexec sidekick container access to the minion's - // docker containers runtime files (e.g. /var/lib/docker/container). This is required - // for argoexec to access the main container's logs and storage to upload output artifacts - hostPathDir = apiv1.HostPathDirectory + + hostPathDir = apiv1.HostPathDirectory + + // volumeDockerLib provides the wait container access to the minion's host docker containers + // runtime files (e.g. /var/lib/docker/container). This is used by the executor to access + // the main container's logs (and potentially storage to upload output artifacts) volumeDockerLib = apiv1.Volume{ Name: common.DockerLibVolumeName, VolumeSource: apiv1.VolumeSource{ @@ -60,6 +62,26 @@ var ( ReadOnly: true, } + // volumeDockerSock provides the wait container direct access to the minion's host docker daemon. + // The primary purpose of this is to make available `docker cp` to collect an output artifact + // from a container. Alternatively, we could use `kubectl cp`, but `docker cp` avoids the extra + // hop to the kube api server. + volumeDockerSock = apiv1.Volume{ + Name: common.DockerSockVolumeName, + VolumeSource: apiv1.VolumeSource{ + HostPath: &apiv1.HostPathVolumeSource{ + Path: "/var/run", + Type: &hostPathDir, + }, + }, + } + volumeMountDockerSock = apiv1.VolumeMount{ + Name: volumeDockerSock.Name, + MountPath: "/var/run/docker.sock", + ReadOnly: true, + SubPath: "docker.sock", + } + // execEnvVars exposes various pod information as environment variables to the exec container execEnvVars = []apiv1.EnvVar{ envFromField(common.EnvVarHostIP, "status.hostIP"), @@ -134,6 +156,7 @@ func (woc *wfOperationCtx) createWorkflowPod(nodeName string, tmpl *wfv1.Templat Volumes: []apiv1.Volume{ volumePodMetadata, volumeDockerLib, + volumeDockerSock, }, }, } @@ -210,14 +233,10 @@ func (woc *wfOperationCtx) newWaitContainer(tmpl *wfv1.Template) (*apiv1.Contain argoExecCmd := fmt.Sprintf(` lineno=$(kubectl get pod $ARGO_POD_NAME -o json | jq -r '.status.containerStatuses | .[] | .name' | grep -n main | awk -F: '{print $1}') index=$(($lineno-1)) - while true ; do - kubectl get pod $ARGO_POD_NAME -o custom-columns=status:status.containerStatuses[$index].state.terminated 2>/dev/null; - if [ $? -eq 0 ] ; then - break; - fi; - echo waiting; - sleep 5; - done && + container_id=$(kubectl get pod $ARGO_POD_NAME -o jsonpath="{.status.containerStatuses[$index].containerID}" | cut -d / -f 3-) + echo "main container_id: $container_id" + docker wait $container_id + echo "container completed" ctrs=$(kubectl get pod $ARGO_POD_NAME -o custom-columns=:status.containerStatuses.*.name | tr "," "\n" | grep -v wait | grep -v main) echo "sidecars: $ctrs" for ctr in $ctrs ; do @@ -228,6 +247,7 @@ func (woc *wfOperationCtx) newWaitContainer(tmpl *wfv1.Template) (*apiv1.Contain ctr.VolumeMounts = []apiv1.VolumeMount{ volumeMountPodMetadata, volumeMountDockerLib, + volumeMountDockerSock, } return ctr, nil } @@ -450,15 +470,10 @@ func addScriptVolume(pod *apiv1.Pod) { ctr.Args = []string{` lineno=$(kubectl get pod $ARGO_POD_NAME -o json | jq -r '.status.containerStatuses | .[] | .name' | grep -n main | awk -F: '{print $1}') index=$(($lineno-1)) - while true ; do - kubectl get pod $ARGO_POD_NAME -o custom-columns=status:status.containerStatuses[$index].state.terminated 2>/dev/null; - if [ $? -eq 0 ] ; then - break; - fi; - echo waiting; - sleep 5; - done && - container_id=$(kubectl get pod $ARGO_POD_NAME -o jsonpath="{.status.containerStatuses[$index].containerID}" | cut -d / -f 3-) && + container_id=$(kubectl get pod $ARGO_POD_NAME -o jsonpath="{.status.containerStatuses[$index].containerID}" | cut -d / -f 3-) + echo "main container_id: $container_id" + docker wait $container_id + echo "container completed" output=$(grep stdout /var/lib/docker/containers/$container_id/*.log | jq -r '.log') && outputjson={\"result\":\"$output\"} && kubectl annotate pods $ARGO_POD_NAME --overwrite workflows.argoproj.io/outputs=${outputjson} &&