Kubernetes CRI CLI: crictl-shaped client, simulator server, proxy
0

Configure Feed

Select the types of activity you want to include in your feed.

cri: drive the server with Kubernetes' own cri-api client

Mirror the etcd clientv3 interop for CRI: test/criclient drives our server with
k8s.io/cri-api (the kubelet's RuntimeService/ImageService client) as the oracle,
over the standard CRI unix socket. run.sh runs two phases:
1. cri-api client -> crictd (the in-memory fake server)
2. cri-api client -> crict-proxy (the transparent allow_all proxy) ->
crictd, or a real runtime via UPSTREAM=<sock> (e.g. containerd)
covering Version/Status, the pod+container lifecycle (run/create/start/status/
list/stop/remove) and the image lifecycle (pull/list/status/remove). Both phases
pass, proving the fake and the proxy are wire-conformant with the real client.

Gated behind CRI_LIVE=1 (dune @criclient alias), SKIPs without a Go toolchain --
the same shape as the existing critest interop.

author
Thomas Gazagnaire
date (Jun 9, 2026, 5:36 PM -0700) commit 034258b9
+368
+219
test/criclient/criclient_test.go
··· 1 + // Interop suite: drive our CRI server (`crictd`) -- and the transparent 2 + // `crict-proxy` -- with Kubernetes' own CRI client library (k8s.io/cri-api, the 3 + // same RuntimeService/ImageService client the kubelet uses). This is the CRI 4 + // equivalent of the etcd clientv3 suite: the real client is the oracle. The 5 + // runner (../criclient/run.sh) starts the server and points CRI_ENDPOINT at its 6 + // unix socket. 7 + package criclient 8 + 9 + import ( 10 + "context" 11 + "net" 12 + "os" 13 + "testing" 14 + "time" 15 + 16 + "google.golang.org/grpc" 17 + "google.golang.org/grpc/credentials/insecure" 18 + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 19 + ) 20 + 21 + func endpoint() string { 22 + if ep := os.Getenv("CRI_ENDPOINT"); ep != "" { 23 + return ep 24 + } 25 + return "/tmp/crid.sock" 26 + } 27 + 28 + func dial(t *testing.T) (runtimeapi.RuntimeServiceClient, runtimeapi.ImageServiceClient) { 29 + sock := endpoint() 30 + conn, err := grpc.Dial("passthrough:///cri", 31 + grpc.WithTransportCredentials(insecure.NewCredentials()), 32 + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { 33 + return net.Dial("unix", sock) 34 + })) 35 + if err != nil { 36 + t.Fatalf("dial %s: %v", sock, err) 37 + } 38 + t.Cleanup(func() { conn.Close() }) 39 + return runtimeapi.NewRuntimeServiceClient(conn), 40 + runtimeapi.NewImageServiceClient(conn) 41 + } 42 + 43 + func bg() (context.Context, context.CancelFunc) { 44 + return context.WithTimeout(context.Background(), 5*time.Second) 45 + } 46 + 47 + func TestVersion(t *testing.T) { 48 + rt, _ := dial(t) 49 + c, cancel := bg() 50 + defer cancel() 51 + 52 + v, err := rt.Version(c, &runtimeapi.VersionRequest{Version: "v1"}) 53 + if err != nil { 54 + t.Fatalf("version: %v", err) 55 + } 56 + if v.RuntimeName == "" || v.Version == "" { 57 + t.Fatalf("version: empty fields %+v", v) 58 + } 59 + } 60 + 61 + func TestImage(t *testing.T) { 62 + _, img := dial(t) 63 + c, cancel := bg() 64 + defer cancel() 65 + 66 + spec := &runtimeapi.ImageSpec{Image: "docker.io/library/busybox:latest"} 67 + pr, err := img.PullImage(c, &runtimeapi.PullImageRequest{Image: spec}) 68 + if err != nil { 69 + t.Fatalf("pull: %v", err) 70 + } 71 + if pr.ImageRef == "" { 72 + t.Fatal("pull: empty image ref") 73 + } 74 + 75 + lr, err := img.ListImages(c, &runtimeapi.ListImagesRequest{}) 76 + if err != nil { 77 + t.Fatalf("list images: %v", err) 78 + } 79 + if len(lr.Images) == 0 { 80 + t.Fatal("list images: empty after pull") 81 + } 82 + 83 + sr, err := img.ImageStatus(c, &runtimeapi.ImageStatusRequest{Image: spec}) 84 + if err != nil { 85 + t.Fatalf("image status: %v", err) 86 + } 87 + if sr.Image == nil { 88 + t.Fatal("image status: nil image after pull") 89 + } 90 + 91 + if _, err := img.RemoveImage(c, &runtimeapi.RemoveImageRequest{Image: spec}); err != nil { 92 + t.Fatalf("remove image: %v", err) 93 + } 94 + } 95 + 96 + func TestPodAndContainer(t *testing.T) { 97 + rt, img := dial(t) 98 + c, cancel := bg() 99 + defer cancel() 100 + 101 + // the image must exist before a container can reference it 102 + imgSpec := &runtimeapi.ImageSpec{Image: "docker.io/library/busybox:latest"} 103 + if _, err := img.PullImage(c, &runtimeapi.PullImageRequest{Image: imgSpec}); err != nil { 104 + t.Fatalf("pull: %v", err) 105 + } 106 + 107 + sandboxCfg := &runtimeapi.PodSandboxConfig{ 108 + Metadata: &runtimeapi.PodSandboxMetadata{ 109 + Name: "p1", 110 + Uid: "uid-p1", 111 + Namespace: "default", 112 + Attempt: 0, 113 + }, 114 + } 115 + rp, err := rt.RunPodSandbox(c, &runtimeapi.RunPodSandboxRequest{Config: sandboxCfg}) 116 + if err != nil { 117 + t.Fatalf("run pod: %v", err) 118 + } 119 + podID := rp.PodSandboxId 120 + if podID == "" { 121 + t.Fatal("run pod: empty id") 122 + } 123 + 124 + pods, err := rt.ListPodSandbox(c, &runtimeapi.ListPodSandboxRequest{}) 125 + if err != nil { 126 + t.Fatalf("list pods: %v", err) 127 + } 128 + if !hasPod(pods.Items, podID) { 129 + t.Fatalf("list pods: %s missing", podID) 130 + } 131 + 132 + cc, err := rt.CreateContainer(c, &runtimeapi.CreateContainerRequest{ 133 + PodSandboxId: podID, 134 + Config: &runtimeapi.ContainerConfig{ 135 + Metadata: &runtimeapi.ContainerMetadata{Name: "c1"}, 136 + Image: imgSpec, 137 + Command: []string{"/bin/sh", "-c", "true"}, 138 + }, 139 + SandboxConfig: sandboxCfg, 140 + }) 141 + if err != nil { 142 + t.Fatalf("create container: %v", err) 143 + } 144 + ctrID := cc.ContainerId 145 + if ctrID == "" { 146 + t.Fatal("create container: empty id") 147 + } 148 + 149 + if _, err := rt.StartContainer(c, &runtimeapi.StartContainerRequest{ContainerId: ctrID}); err != nil { 150 + t.Fatalf("start container: %v", err) 151 + } 152 + 153 + st, err := rt.ContainerStatus(c, &runtimeapi.ContainerStatusRequest{ContainerId: ctrID}) 154 + if err != nil { 155 + t.Fatalf("container status: %v", err) 156 + } 157 + if st.Status.State != runtimeapi.ContainerState_CONTAINER_RUNNING { 158 + t.Fatalf("container status: want RUNNING got %v", st.Status.State) 159 + } 160 + 161 + ctrs, err := rt.ListContainers(c, &runtimeapi.ListContainersRequest{}) 162 + if err != nil { 163 + t.Fatalf("list containers: %v", err) 164 + } 165 + if !hasContainer(ctrs.Containers, ctrID) { 166 + t.Fatalf("list containers: %s missing", ctrID) 167 + } 168 + 169 + if _, err := rt.StopContainer(c, &runtimeapi.StopContainerRequest{ContainerId: ctrID, Timeout: 0}); err != nil { 170 + t.Fatalf("stop container: %v", err) 171 + } 172 + if _, err := rt.RemoveContainer(c, &runtimeapi.RemoveContainerRequest{ContainerId: ctrID}); err != nil { 173 + t.Fatalf("remove container: %v", err) 174 + } 175 + if _, err := rt.StopPodSandbox(c, &runtimeapi.StopPodSandboxRequest{PodSandboxId: podID}); err != nil { 176 + t.Fatalf("stop pod: %v", err) 177 + } 178 + if _, err := rt.RemovePodSandbox(c, &runtimeapi.RemovePodSandboxRequest{PodSandboxId: podID}); err != nil { 179 + t.Fatalf("remove pod: %v", err) 180 + } 181 + 182 + // the pod is gone after removal 183 + pods, _ = rt.ListPodSandbox(c, &runtimeapi.ListPodSandboxRequest{}) 184 + if hasPod(pods.Items, podID) { 185 + t.Fatalf("list pods: %s still present after remove", podID) 186 + } 187 + } 188 + 189 + func TestStatus(t *testing.T) { 190 + rt, _ := dial(t) 191 + c, cancel := bg() 192 + defer cancel() 193 + 194 + st, err := rt.Status(c, &runtimeapi.StatusRequest{}) 195 + if err != nil { 196 + t.Fatalf("status: %v", err) 197 + } 198 + if st.Status == nil { 199 + t.Fatal("status: nil") 200 + } 201 + } 202 + 203 + func hasPod(items []*runtimeapi.PodSandbox, id string) bool { 204 + for _, p := range items { 205 + if p.Id == id { 206 + return true 207 + } 208 + } 209 + return false 210 + } 211 + 212 + func hasContainer(items []*runtimeapi.Container, id string) bool { 213 + for _, ct := range items { 214 + if ct.Id == id { 215 + return true 216 + } 217 + } 218 + return false 219 + }
+26
test/criclient/dune
··· 1 + ; Drive the CRI server with Kubernetes' own cri-api client. Gated behind 2 + ; CRI_LIVE=1 so it never runs during a normal `dune test`; run it with: 3 + ; CRI_LIVE=1 dune build @criclient 4 + ; or, with the proxy in front of a real runtime: 5 + ; UPSTREAM=/run/containerd/containerd.sock CRI_LIVE=1 dune build @criclient 6 + ; The script SKIPs (exit 0) when the Go toolchain is not installed. 7 + 8 + (rule 9 + (alias criclient) 10 + (enabled_if 11 + (= %{env:CRI_LIVE=0} 1)) 12 + (deps 13 + run.sh 14 + criclient_test.go 15 + go.mod 16 + go.sum 17 + %{bin:crictd} 18 + %{bin:crict-proxy}) 19 + (action 20 + (setenv 21 + CRICTD 22 + %{bin:crictd} 23 + (setenv 24 + CRICT_PROXY 25 + %{bin:crict-proxy} 26 + (run sh run.sh)))))
+18
test/criclient/go.mod
··· 1 + module cri-interop-criclient 2 + 3 + go 1.21 4 + 5 + require ( 6 + google.golang.org/grpc v1.59.0 7 + k8s.io/cri-api v0.29.0 8 + ) 9 + 10 + require ( 11 + github.com/gogo/protobuf v1.3.2 // indirect 12 + github.com/golang/protobuf v1.5.3 // indirect 13 + golang.org/x/net v0.17.0 // indirect 14 + golang.org/x/sys v0.13.0 // indirect 15 + golang.org/x/text v0.13.0 // indirect 16 + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect 17 + google.golang.org/protobuf v1.31.0 // indirect 18 + )
+53
test/criclient/go.sum
··· 1 + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 2 + github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 3 + github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 4 + github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 5 + github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 6 + github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 7 + github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 + github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 + github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 10 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 11 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 12 + github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 13 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 14 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 15 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 16 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 17 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 18 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 19 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 20 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 + golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 22 + golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 23 + golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 24 + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 + golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 31 + golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 33 + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 34 + golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 35 + golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 36 + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 37 + golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 38 + golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 39 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 40 + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 41 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 42 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 43 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 44 + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= 45 + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= 46 + google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 47 + google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 48 + google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 49 + google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 50 + google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 51 + google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 52 + k8s.io/cri-api v0.29.0 h1:atenAqOltRsFqcCQlFFpDnl/R4aGfOELoNLTDJfd7t8= 53 + k8s.io/cri-api v0.29.0/go.mod h1:Rls2JoVwfC7kW3tndm7267kriuRukQ02qfht0PCRuIc=
+52
test/criclient/run.sh
··· 1 + #!/bin/sh 2 + # Drive our CRI server with Kubernetes' own CRI client (k8s.io/cri-api, the 3 + # kubelet's RuntimeService/ImageService client) -- the CRI equivalent of the 4 + # etcd clientv3 suite. Two phases: 5 + # 1. cri-api client -> crictd (the in-memory fake server) 6 + # 2. cri-api client -> crict-proxy (the transparent allow_all proxy) -> 7 + # crictd, or a real runtime when UPSTREAM=<sock> is set (e.g. containerd). 8 + # Phase 2 puts our codecs + proxy in the request path, so a passing run proves 9 + # the proxy forwards every RPC faithfully. Needs a Go toolchain; SKIPs cleanly 10 + # when absent. 11 + set -eu 12 + ROOT="$(git rev-parse --show-toplevel)" 13 + command -v go >/dev/null 2>&1 || { echo "SKIP: go not on PATH"; exit 0; } 14 + 15 + # CRICTD / CRICT_PROXY are injected by the dune rule (%{bin:...}); default to the 16 + # build tree for a direct `sh run.sh`. 17 + CRICTD="${CRICTD:-$ROOT/_build/default/ocaml-cri/bin/server/main.exe}" 18 + CRICT_PROXY="${CRICT_PROXY:-$ROOT/_build/default/ocaml-cri/bin/proxy/main.exe}" 19 + [ -x "$CRICTD" ] || dune build ocaml-cri/bin/server/main.exe ocaml-cri/bin/proxy/main.exe 20 + SUITE="$ROOT/ocaml-cri/test/criclient" 21 + run_suite() { 22 + (cd "$SUITE" && GOFLAGS=-mod=mod CRI_ENDPOINT="$1" go test -count=1 -v ./...) 23 + } 24 + 25 + FAKE="$(mktemp -u "${TMPDIR:-/tmp}/crid.XXXXXX")" 26 + PSOCK="$(mktemp -u "${TMPDIR:-/tmp}/crid-proxy.XXXXXX")" 27 + cleanup() { 28 + [ -n "${SP:-}" ] && kill "$SP" 2>/dev/null || true 29 + [ -n "${PP:-}" ] && kill "$PP" 2>/dev/null || true 30 + rm -f "$FAKE" "$PSOCK" 31 + } 32 + trap cleanup EXIT 33 + 34 + wait_sock() { 35 + i=0 36 + while [ ! -S "$1" ] && [ "$i" -lt 50 ]; do i=$((i + 1)); sleep 0.1; done 37 + } 38 + 39 + echo "== 1. cri-api client -> crictd (in-memory fake) ==" 40 + "$CRICTD" --socket "$FAKE" >/tmp/crictd.log 2>&1 & SP=$! 41 + wait_sock "$FAKE" 42 + run_suite "$FAKE" || { echo "FAIL: cri-api -> crictd"; cat /tmp/crictd.log; exit 1; } 43 + echo " cri-api <-> crictd: PASS" 44 + 45 + UP="${UPSTREAM:-$FAKE}" 46 + echo "== 2. cri-api client -> crict-proxy(allow_all) -> $UP ==" 47 + "$CRICT_PROXY" --listen "$PSOCK" --upstream "$UP" --policy allow_all \ 48 + >/tmp/crict-proxy.log 2>&1 & PP=$! 49 + wait_sock "$PSOCK" 50 + run_suite "$PSOCK" || 51 + { echo "FAIL: cri-api -> crict-proxy -> $UP"; cat /tmp/crict-proxy.log; exit 1; } 52 + echo " cri-api <-> crict-proxy <-> $UP: PASS"