diff --git a/spec/fixtures/metrics-server.yaml b/spec/fixtures/metrics-server.yaml new file mode 100644 index 000000000..33f541718 --- /dev/null +++ b/spec/fixtures/metrics-server.yaml @@ -0,0 +1,150 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:aggregated-metrics-reader + labels: + rbac.authorization.k8s.io/aggregate-to-view: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: +- apiGroups: ["metrics.k8s.io"] + resources: ["pods", "nodes"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metrics-server:system:auth-delegator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: metrics-server + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: metrics-server-auth-reader + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: extension-apiserver-authentication-reader +subjects: +- kind: ServiceAccount + name: metrics-server + namespace: kube-system +--- +apiVersion: apiregistration.k8s.io/v1beta1 +kind: APIService +metadata: + name: v1beta1.metrics.k8s.io +spec: + service: + name: metrics-server + namespace: kube-system + group: metrics.k8s.io + version: v1beta1 + insecureSkipTLSVerify: true + groupPriorityMinimum: 100 + versionPriority: 100 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: metrics-server + namespace: kube-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: metrics-server + namespace: kube-system + labels: + k8s-app: metrics-server +spec: + selector: + matchLabels: + k8s-app: metrics-server + template: + metadata: + name: metrics-server + labels: + k8s-app: metrics-server + spec: + serviceAccountName: metrics-server + volumes: + # mount in tmp so we can safely use from-scratch images and/or read-only containers + - name: tmp-dir + emptyDir: {} + containers: + - name: metrics-server + image: bitnami/metrics-server:latest + imagePullPolicy: IfNotPresent + args: + - --cert-dir=/tmp + - --secure-port=4443 + ports: + - name: main-port + containerPort: 4443 + protocol: TCP + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + volumeMounts: + - name: tmp-dir + mountPath: /tmp + nodeSelector: + kubernetes.io/os: linux +--- +apiVersion: v1 +kind: Service +metadata: + name: metrics-server + namespace: kube-system + labels: + kubernetes.io/name: "Metrics-server" + kubernetes.io/cluster-service: "true" +spec: + selector: + k8s-app: metrics-server + ports: + - port: 443 + protocol: TCP + targetPort: main-port +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:metrics-server +rules: +- apiGroups: + - "" + resources: + - pods + - nodes + - nodes/stats + - namespaces + - configmaps + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:metrics-server +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:metrics-server +subjects: +- kind: ServiceAccount + name: metrics-server + namespace: kube-system diff --git a/spec/platform/hardware_and_scheduler.cr b/spec/platform/hardware_and_scheduler_spec.cr similarity index 100% rename from spec/platform/hardware_and_scheduler.cr rename to spec/platform/hardware_and_scheduler_spec.cr diff --git a/spec/platform/observability_spec.cr b/spec/platform/observability_spec.cr new file mode 100644 index 000000000..73e589572 --- /dev/null +++ b/spec/platform/observability_spec.cr @@ -0,0 +1,82 @@ +require "./../spec_helper" +require "colorize" +require "./../../src/tasks/utils/utils.cr" + +describe "Observability" do + before_all do + current_dir = FileUtils.pwd + LOGGING.info current_dir + helm = "#{current_dir}/#{TOOLS_DIR}/helm/linux-amd64/helm" + LOGGING.info "Installing kube_state_metrics" + resp = `#{helm} install kube-state-metrics stable/kube-state-metrics` + LOGGING.info resp + CNFManager.wait_for_install("kube-state-metrics") + + LOGGING.info "Installing prometheus-node-exporter" + resp = `#{helm} install node-exporter stable/prometheus-node-exporter` + LOGGING.info resp + + LOGGING.info "Installing prometheus-adapter" + resp = `#{helm} install prometheus-adapter stable/prometheus-adapter` + LOGGING.info resp + CNFManager.wait_for_install("prometheus-adapter") + + LOGGING.info "Installing metrics_server" + resp = `kubectl create -f spec/fixtures/metrics-server.yaml` + LOGGING.info resp + CNFManager.wait_for_install(deployment_name: "metrics-server", namespace:"kube-system") + # The next line seems to avoid: "Error running at_exit handler" "Invalid Int32" + 0 + end + + after_all do + current_dir = FileUtils.pwd + LOGGING.info current_dir + helm = "#{current_dir}/#{TOOLS_DIR}/helm/linux-amd64/helm" + resp = `#{helm} delete kube-state-metrics` + LOGGING.info resp + $?.success?.should be_true + resp = `#{helm} delete node-exporter` + LOGGING.info resp + $?.success?.should be_true + resp = `#{helm} delete prometheus-adapter` + LOGGING.info resp + $?.success?.should be_true + resp = `kubectl delete -f spec/fixtures/metrics-server.yaml` + LOGGING.info resp + $?.success?.should be_true + end + + it "'kube_state_metrics' should return some json", tags: "platform:kube_state_metrics" do + response_s = `./cnf-conformance platform:kube_state_metrics poc` + LOGGING.info response_s + (/(PASSED){1}.*(Your platform is using the){1}.*(release for kube state metrics){1}/ =~ response_s).should_not be_nil + end + + it "'node_exporter' should detect the named release of the installed node_exporter", tags: "platform:node_exporter" do + pod_ready = "" + pod_ready_timeout = 45 + until (pod_ready == "true" || pod_ready_timeout == 0) + pod_ready = CNFManager.pod_status("node-exporter-prometheus").split(",")[2] + puts "Pod Ready Status: #{pod_ready}" + sleep 1 + pod_ready_timeout = pod_ready_timeout - 1 + end + response_s = `./cnf-conformance platform:node_exporter poc` + LOGGING.info response_s + (/(PASSED){1}.*(Your platform is using the){1}.*(release for the node exporter){1}/ =~ response_s).should_not be_nil + end + + it "'prometheus_adapter' should detect the named release of the installed prometheus_adapter", tags: "platform:prometheus_adapter" do + response_s = `./cnf-conformance platform:prometheus_adapter poc` + LOGGING.info response_s + (/(PASSED){1}.*(Your platform is using the){1}.*(release for the prometheus adapter){1}/ =~ response_s).should_not be_nil + end + + it "'metrics_server' should detect the named release of the installed metrics_server", tags: "platform:metrics_server" do + response_s = `./cnf-conformance platform:metrics_server poc` + LOGGING.info response_s + (/(PASSED){1}.*(Your platform is using the){1}.*(release for the metrics server){1}/ =~ response_s).should_not be_nil + end +end + diff --git a/src/tasks/platform/observability.cr b/src/tasks/platform/observability.cr new file mode 100644 index 000000000..328da0cf5 --- /dev/null +++ b/src/tasks/platform/observability.cr @@ -0,0 +1,276 @@ +# coding: utf-8 +require "sam" +require "colorize" +require "../utils/utils.cr" + +namespace "platform" do + desc "The CNF conformance suite checks to see if the Platform has Observability support." + task "observability", ["kube_state_metrics", "node_exporter", "prometheus_adapter", "metrics_server"] do |t, args| + VERBOSE_LOGGING.info "resilience" if check_verbose(args) + VERBOSE_LOGGING.debug "resilience args.raw: #{args.raw}" if check_verbose(args) + VERBOSE_LOGGING.debug "resilience args.named: #{args.named}" if check_verbose(args) + stdout_score("platform:resilience") + end + + desc "Does the Platform have Kube State Metrics installed" + task "kube_state_metrics" do |_, args| + unless check_poc(args) + LOGGING.info "skipping kube_state_metrics: not in poc mode" + puts "Skipped".colorize(:yellow) + next + end + LOGGING.info "Running POC: kube_state_metrics" + task_response = task_runner(args) do |args| + current_dir = FileUtils.pwd + + state_metric_releases = `curl -L -s https://quay.io/api/v1/repository/coreos/kube-state-metrics/tag/?limit=100` + # Get the sha hash for the kube-state-metrics container + sha_list = named_sha_list(state_metric_releases) + LOGGING.debug "sha_list: #{sha_list}" + + # TODO find hash for image + imageids = KubectlClient::Get.all_container_repo_digests + LOGGING.debug "imageids: #{imageids}" + found = false + release_name = "" + sha_list.each do |x| + if imageids.find{|i| i.includes?(x["manifest_digest"])} + found = true + release_name = x["name"] + end + end + if found + emoji_kube_state_metrics="📶☠️" + upsert_passed_task("kube_state_metrics","✔️ PASSED: Your platform is using the #{release_name} release for kube state metrics #{emoji_kube_state_metrics}") + else + emoji_kube_state_metrics="📶☠️" + upsert_failed_task("kube_state_metrics", "✖️ FAILURE: Your platform does not have kube state metrics installed #{emoji_kube_state_metrics}") + end + end + end + + desc "Does the Platform have a Node Exporter installed" + task "node_exporter" do |_, args| + unless check_poc(args) + LOGGING.info "skipping node_exporter: not in poc mode" + puts "Skipped".colorize(:yellow) + next + end + LOGGING.info "Running POC: node_exporter" + task_response = task_runner(args) do |args| + + #Select the first node that isn't a master and is also schedulable + #worker_nodes = `kubectl get nodes --selector='!node-role.kubernetes.io/master' -o 'go-template={{range .items}}{{$taints:=""}}{{range .spec.taints}}{{if eq .effect "NoSchedule"}}{{$taints = print $taints .key ","}}{{end}}{{end}}{{if not $taints}}{{.metadata.name}}{{ "\\n"}}{{end}}{{end}}'` + #worker_node = worker_nodes.split("\n")[0] + + # Install and find CRI Tools name + File.write("cri_tools.yml", CRI_TOOLS) + install_cri_tools = `kubectl create -f cri_tools.yml` + cri_tools_pod = CNFManager.pod_status("cri-tools").split(",")[0] + #, "--field-selector spec.nodeName=#{worker_node}") + LOGGING.debug "cri_tools_pod: #{cri_tools_pod}" + + # Fetch id sha256 sums for all repo_digests https://github.com/docker/distribution/issues/1662 + repo_digest_list = KubectlClient::Get.all_container_repo_digests + LOGGING.info "container_repo_digests: #{repo_digest_list}" + id_sha256_list = repo_digest_list.reduce([] of String) do |acc, repo_digest| + LOGGING.debug "repo_digest: #{repo_digest}" + cricti = `kubectl exec -ti #{cri_tools_pod} crictl inspecti #{repo_digest}` + LOGGING.debug "cricti: #{cricti}" + parsed_json = JSON.parse(cricti) + acc << parsed_json["status"]["id"].as_s + end + LOGGING.debug "id_sha256_list: #{id_sha256_list}" + + + # Fetch image id sha256sums available for all upstream node-exporter releases + node_exporter_releases = `curl -L -s 'https://registry.hub.docker.com/v2/repositories/prom/node-exporter/tags?page_size=1024'` + tag_list = named_sha_list(node_exporter_releases) + LOGGING.info "tag_list: #{tag_list}" + if ENV["DOCKERHUB_USERNAME"]? && ENV["DOCKERHUB_PASSWORD"]? + target_ns_repo = "prom/node-exporter" + params = "service=registry.docker.io&scope=repository:#{target_ns_repo}:pull" + token = `curl --user "#{ENV["DOCKERHUB_USERNAME"]}:#{ENV["DOCKERHUB_PASSWORD"]}" "https://auth.docker.io/token?#{params}"` + parsed_token = JSON.parse(token) + release_id_list = tag_list.reduce([] of Hash(String, String)) do |acc, tag| + LOGGING.debug "tag: #{tag}" + tag = tag["name"] + + image_id = `curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" "https://registry-1.docker.io/v2/#{target_ns_repo}/manifests/#{tag}" -H "Authorization:Bearer #{parsed_token["token"].as_s}"` + parsed_image = JSON.parse(image_id) + + LOGGING.debug "parsed_image config digest #{parsed_image["config"]["digest"]}" + if parsed_image["config"]["digest"]? + acc << {"name" => tag, "digest"=> parsed_image["config"]["digest"].as_s} + else + acc + end + end + else + puts "DOCKERHUB_USERNAME & DOCKERHUB_PASSWORD Must be set." + exit 1 + end + LOGGING.debug "Release id sha256sum list: #{release_id_list}" + + found = false + release_name = "" + release_id_list.each do |x| + if id_sha256_list.find{|i| i.includes?(x["digest"])} + found = true + release_name = x["name"] + end + end + if found + emoji_node_exporter="📶☠️" + upsert_passed_task("node_exporter","✔️ PASSED: Your platform is using the #{release_name} release for the node exporter #{emoji_node_exporter}") + else + emoji_node_exporter="📶☠️" + upsert_failed_task("node_exporter", "✖️ FAILURE: Your platform does not have the node exporter installed #{emoji_node_exporter}") + end + end + end +end + + + desc "Does the Platform have the prometheus adapter installed" + task "prometheus_adapter" do |_, args| + unless check_poc(args) + LOGGING.info "skipping prometheus_adapter: not in poc mode" + puts "Skipped".colorize(:yellow) + next + end + LOGGING.info "Running POC: prometheus_adapter" + task_response = task_runner(args) do |args| + # Fetch image id sha256sums available for all upstream prometheus_adapter releases + prometheus_adapter_releases = `curl -L -s 'https://registry.hub.docker.com/v2/repositories/directxman12/k8s-prometheus-adapter-amd64/tags?page_size=1024'` + sha_list = named_sha_list(prometheus_adapter_releases) + LOGGING.debug "sha_list: #{sha_list}" + + # TODO find hash for image + imageids = KubectlClient::Get.all_container_repo_digests + LOGGING.debug "imageids: #{imageids}" + found = false + release_name = "" + sha_list.each do |x| + if imageids.find{|i| i.includes?(x["manifest_digest"])} + found = true + release_name = x["name"] + end + end + + if found + emoji_prometheus_adapter="📶☠️" + upsert_passed_task("prometheus_adapter","✔️ PASSED: Your platform is using the #{release_name} release for the prometheus adapter #{emoji_prometheus_adapter}") + else + emoji_prometheus_adapter="📶☠️" + upsert_failed_task("prometheus_adapter", "✖️ FAILURE: Your platform does not have the prometheus adapter installed #{emoji_prometheus_adapter}") + end + end + end + + desc "Does the Platform have the K8s Metrics Server installed" + task "metrics_server" do |_, args| + unless check_poc(args) + LOGGING.info "skipping metrics_server: not in poc mode" + puts "Skipped".colorize(:yellow) + next + end + LOGGING.info "Running POC: metrics_server" + task_response = task_runner(args) do |args| + + #Select the first node that isn't a master and is also schedulable + #worker_nodes = `kubectl get nodes --selector='!node-role.kubernetes.io/master' -o 'go-template={{range .items}}{{$taints:=""}}{{range .spec.taints}}{{if eq .effect "NoSchedule"}}{{$taints = print $taints .key ","}}{{end}}{{end}}{{if not $taints}}{{.metadata.name}}{{ "\\n"}}{{end}}{{end}}'` + #worker_node = worker_nodes.split("\n")[0] + + # Install and find CRI Tools name + File.write("cri_tools.yml", CRI_TOOLS) + install_cri_tools = `kubectl create -f cri_tools.yml` + cri_tools_pod = CNFManager.pod_status("cri-tools").split(",")[0] + #, "--field-selector spec.nodeName=#{worker_node}") + LOGGING.debug "cri_tools_pod: #{cri_tools_pod}" + + # Fetch id sha256 sums for all repo_digests https://github.com/docker/distribution/issues/1662 + repo_digest_list = KubectlClient::Get.all_container_repo_digests + LOGGING.info "container_repo_digests: #{repo_digest_list}" + id_sha256_list = repo_digest_list.reduce([] of String) do |acc, repo_digest| + LOGGING.debug "repo_digest: #{repo_digest}" + cricti = `kubectl exec -ti #{cri_tools_pod} crictl inspecti #{repo_digest}` + LOGGING.debug "cricti: #{cricti}" + parsed_json = JSON.parse(cricti) + acc << parsed_json["status"]["id"].as_s + end + LOGGING.info "id_sha256_list: #{id_sha256_list}" + + + # Fetch image id sha256sums available for all upstream node-exporter releases + metrics_server_releases = `curl -L -s 'https://registry.hub.docker.com/v2/repositories/bitnami/metrics-server/tags?page=1'` + tag_list = named_sha_list(metrics_server_releases) + LOGGING.info "tag_list: #{tag_list}" + if ENV["DOCKERHUB_USERNAME"]? && ENV["DOCKERHUB_PASSWORD"]? + target_ns_repo = "bitnami/metrics-server" + params = "service=registry.docker.io&scope=repository:#{target_ns_repo}:pull" + token = `curl --user "#{ENV["DOCKERHUB_USERNAME"]}:#{ENV["DOCKERHUB_PASSWORD"]}" "https://auth.docker.io/token?#{params}"` + parsed_token = JSON.parse(token) + release_id_list = tag_list.reduce([] of Hash(String, String)) do |acc, tag| + LOGGING.debug "tag: #{tag}" + tag = tag["name"] + + image_id = `curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" "https://registry-1.docker.io/v2/#{target_ns_repo}/manifests/#{tag}" -H "Authorization:Bearer #{parsed_token["token"].as_s}"` + parsed_image = JSON.parse(image_id) + + LOGGING.debug "parsed_image config digest #{parsed_image["config"]["digest"]}" + if parsed_image["config"]["digest"]? + acc << {"name" => tag, "digest"=> parsed_image["config"]["digest"].as_s} + else + acc + end + end + else + puts "DOCKERHUB_USERNAME & DOCKERHUB_PASSWORD Must be set." + exit 1 + end + LOGGING.info "Release id sha256sum list: #{release_id_list}" + + found = false + release_name = "" + release_id_list.each do |x| + if id_sha256_list.find{|i| i.includes?(x["digest"])} + found = true + release_name = x["name"] + end + end + if found + emoji_metrics_server="📶☠️" + upsert_passed_task("metrics_server","✔️ PASSED: Your platform is using the #{release_name} release for the metrics server #{emoji_metrics_server}") + else + emoji_metrics_server="📶☠️" + upsert_failed_task("metrics_server", "✖️ FAILURE: Your platform does not have the metrics server installed #{emoji_metrics_server}") + end + end + end + + + +def named_sha_list(resp_json) + LOGGING.debug "sha_list resp_json: #{resp_json}" + parsed_json = JSON.parse(resp_json) + LOGGING.debug "sha list parsed json: #{parsed_json}" + #if tags then this is a quay repository, otherwise assume docker hub repository + if parsed_json["tags"]? + parsed_json["tags"].not_nil!.as_a.reduce([] of Hash(String, String)) do |acc, i| + acc << {"name" => i["name"].not_nil!.as_s, "manifest_digest" => i["manifest_digest"].not_nil!.as_s} + end + else + parsed_json["results"].not_nil!.as_a.reduce([] of Hash(String, String)) do |acc, i| + #TODO always use amd64 + amd64image = i["images"].as_a.find{|x| x["architecture"].as_s == "amd64"} + LOGGING.debug "amd64image: #{amd64image}" + if amd64image && amd64image["digest"]? + acc << {"name" => i["name"].not_nil!.as_s, "manifest_digest" => amd64image["digest"].not_nil!.as_s} + else + LOGGING.error "amd64 image not found in #{i["images"]}" + acc + end + end + end +end diff --git a/src/tasks/utils/embedded_file_manager.cr b/src/tasks/utils/embedded_file_manager.cr index e9a109886..b3efb9818 100644 --- a/src/tasks/utils/embedded_file_manager.cr +++ b/src/tasks/utils/embedded_file_manager.cr @@ -6,7 +6,10 @@ require "halite" module EmbeddedFileManager macro reboot_daemon - REBOOT_DAEMON = File.read("./tools/reboot_daemon/manifest.yml") + REBOOT_DAEMON = File.read("./tools/reboot_daemon/manifest.yml") + end + macro cri_tools + CRI_TOOLS = File.read("./tools/cri-tools/manifest.yml") end macro node_failure_values NODE_FAILURE_VALUES = File.read("./embedded_files/node_failure_values.yml") diff --git a/src/tasks/utils/kubectl_client.cr b/src/tasks/utils/kubectl_client.cr index cde9e6168..8af09862a 100644 --- a/src/tasks/utils/kubectl_client.cr +++ b/src/tasks/utils/kubectl_client.cr @@ -8,6 +8,7 @@ module KubectlClient OCI_RUNTIME_REGEX = /containerd|docker|runc|railcar|crun|rkt|gviso|nabla|runv|clearcontainers|kata|cri-o/i module Get def self.nodes : JSON::Any + # TODO should this be all namespaces? resp = `kubectl get nodes -o json` LOGGING.info "kubectl get nodes: #{resp}" JSON.parse(resp) @@ -22,5 +23,32 @@ module KubectlClient LOGGING.info "runtimes: #{runtimes}" runtimes.uniq end + def self.pods : JSON::Any + resp = `kubectl get pods --all-namespaces -o json` + LOGGING.debug "kubectl get pods: #{resp}" + JSON.parse(resp) + end + def self.all_pod_statuses + statuses = pods["items"].as_a.map do |x| + x["status"] + end + LOGGING.debug "pod statuses: #{statuses}" + statuses + end + def self.all_pod_container_statuses + statuses = all_pod_statuses.map do |x| + x["containerStatuses"].as_a + end + # LOGGING.info "pod container statuses: #{statuses}" + statuses + end + def self.all_container_repo_digests + imageids = all_pod_container_statuses.reduce([] of String) do |acc, x| + # acc << "hi" + acc | x.map{|i| i["imageID"].as_s} + end + LOGGING.debug "pod container image ids: #{imageids}" + imageids + end end end diff --git a/src/tasks/utils/utils.cr b/src/tasks/utils/utils.cr index 4261be526..1cf332f04 100644 --- a/src/tasks/utils/utils.cr +++ b/src/tasks/utils/utils.cr @@ -26,6 +26,7 @@ PRIVILEGED_WHITELIST_CONTAINERS = ["chaos-daemon"] #Embedded global text variables EmbeddedFileManager.reboot_daemon EmbeddedFileManager.node_failure_values +EmbeddedFileManager.cri_tools def log_formatter Log::Formatter.new do |entry, io| diff --git a/tools/cri-tools/Dockerfile b/tools/cri-tools/Dockerfile new file mode 100644 index 000000000..b83816c34 --- /dev/null +++ b/tools/cri-tools/Dockerfile @@ -0,0 +1,13 @@ +FROM debian:latest + +ENV VERSION="v1.17.0" +ENV CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock +ENV IMAGE_SERVICE_ENDPOINT=unix:///run/containerd/containerd.sock + +RUN apt update && apt install -y curl +RUN curl -L https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-${VERSION}-linux-amd64.tar.gz --output crictl-${VERSION}-linux-amd64.tar.gz +RUN tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin +RUN rm -f crictl-$VERSION-linux-amd64.tar.gz + + + diff --git a/tools/cri-tools/manifest.yml b/tools/cri-tools/manifest.yml new file mode 100644 index 000000000..d16c4ed3b --- /dev/null +++ b/tools/cri-tools/manifest.yml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: cri-tools +spec: + selector: + matchLabels: + name: cri-tools + template: + metadata: + labels: + name: cri-tools + spec: + containers: + - name: cri-tools + image: conformance/cri-tools:latest + command: ["/bin/sh"] + args: ["-c", "sleep infinity"] + volumeMounts: + - mountPath: /run/containerd/containerd.sock + name: containerd-volume + volumes: + - name: containerd-volume + hostPath: + path: /var/run/containerd/containerd.sock