1
16
17 package runtime
18
19 import (
20 "os"
21 "strings"
22
23 "github.com/pkg/errors"
24
25 errorsutil "k8s.io/apimachinery/pkg/util/errors"
26 "k8s.io/klog/v2"
27 utilsexec "k8s.io/utils/exec"
28
29 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
30 )
31
32
33 var defaultKnownCRISockets = []string{
34 constants.CRISocketContainerd,
35 constants.CRISocketCRIO,
36 constants.CRISocketDocker,
37 }
38
39
40 type ContainerRuntime interface {
41 Socket() string
42 IsRunning() error
43 ListKubeContainers() ([]string, error)
44 RemoveContainers(containers []string) error
45 PullImage(image string) error
46 PullImagesInParallel(images []string, ifNotPresent bool) error
47 ImageExists(image string) (bool, error)
48 SandboxImage() (string, error)
49 }
50
51
52 type CRIRuntime struct {
53 exec utilsexec.Interface
54 criSocket string
55 crictlPath string
56 }
57
58
59 func NewContainerRuntime(execer utilsexec.Interface, criSocket string) (ContainerRuntime, error) {
60 const toolName = "crictl"
61 crictlPath, err := execer.LookPath(toolName)
62 if err != nil {
63 return nil, errors.Wrapf(err, "%s is required by the container runtime", toolName)
64 }
65 return &CRIRuntime{execer, criSocket, crictlPath}, nil
66 }
67
68
69 func (runtime *CRIRuntime) Socket() string {
70 return runtime.criSocket
71 }
72
73
74 func (runtime *CRIRuntime) crictl(args ...string) utilsexec.Cmd {
75 cmd := runtime.exec.Command(runtime.crictlPath, append([]string{"-r", runtime.Socket(), "-i", runtime.Socket()}, args...)...)
76 cmd.SetEnv(os.Environ())
77 return cmd
78 }
79
80
81 func (runtime *CRIRuntime) IsRunning() error {
82 if out, err := runtime.crictl("info").CombinedOutput(); err != nil {
83 return errors.Wrapf(err, "container runtime is not running: output: %s, error", string(out))
84 }
85 return nil
86 }
87
88
89 func (runtime *CRIRuntime) ListKubeContainers() ([]string, error) {
90
91
92 args := []string{"-D=false", "pods", "-q"}
93 out, err := runtime.crictl(args...).CombinedOutput()
94 if err != nil {
95 return nil, errors.Wrapf(err, "output: %s, error", string(out))
96 }
97 pods := []string{}
98 pods = append(pods, strings.Fields(string(out))...)
99 return pods, nil
100 }
101
102
103 func (runtime *CRIRuntime) RemoveContainers(containers []string) error {
104 errs := []error{}
105 for _, container := range containers {
106 var lastErr error
107 for i := 0; i < constants.RemoveContainerRetry; i++ {
108 klog.V(5).Infof("Attempting to remove container %v", container)
109 out, err := runtime.crictl("stopp", container).CombinedOutput()
110 if err != nil {
111 lastErr = errors.Wrapf(err, "failed to stop running pod %s: output: %s", container, string(out))
112 continue
113 }
114 out, err = runtime.crictl("rmp", container).CombinedOutput()
115 if err != nil {
116 lastErr = errors.Wrapf(err, "failed to remove running container %s: output: %s", container, string(out))
117 continue
118 }
119 lastErr = nil
120 break
121 }
122
123 if lastErr != nil {
124 errs = append(errs, lastErr)
125 }
126 }
127 return errorsutil.NewAggregate(errs)
128 }
129
130
131 func (runtime *CRIRuntime) PullImage(image string) error {
132 var err error
133 var out []byte
134 for i := 0; i < constants.PullImageRetry; i++ {
135 out, err = runtime.crictl("pull", image).CombinedOutput()
136 if err == nil {
137 return nil
138 }
139 }
140 return errors.Wrapf(err, "output: %s, error", out)
141 }
142
143
144 func (runtime *CRIRuntime) PullImagesInParallel(images []string, ifNotPresent bool) error {
145 errs := pullImagesInParallelImpl(images, ifNotPresent, runtime.ImageExists, runtime.PullImage)
146 return errorsutil.NewAggregate(errs)
147 }
148
149 func pullImagesInParallelImpl(images []string, ifNotPresent bool,
150 imageExistsFunc func(string) (bool, error), pullImageFunc func(string) error) []error {
151
152 var errs []error
153 errChan := make(chan error, len(images))
154
155 klog.V(1).Info("pulling all images in parallel")
156 for _, img := range images {
157 image := img
158 go func() {
159 if ifNotPresent {
160 exists, err := imageExistsFunc(image)
161 if err != nil {
162 errChan <- errors.WithMessagef(err, "failed to check if image %s exists", image)
163 return
164 }
165 if exists {
166 klog.V(1).Infof("image exists: %s", image)
167 errChan <- nil
168 return
169 }
170 }
171 err := pullImageFunc(image)
172 if err != nil {
173 err = errors.WithMessagef(err, "failed to pull image %s", image)
174 } else {
175 klog.V(1).Infof("done pulling: %s", image)
176 }
177 errChan <- err
178 }()
179 }
180
181 for i := 0; i < len(images); i++ {
182 if err := <-errChan; err != nil {
183 errs = append(errs, err)
184 }
185 }
186
187 return errs
188 }
189
190
191 func (runtime *CRIRuntime) ImageExists(image string) (bool, error) {
192 err := runtime.crictl("inspecti", image).Run()
193 return err == nil, nil
194 }
195
196
197 func detectCRISocketImpl(isSocket func(string) bool, knownCRISockets []string) (string, error) {
198 foundCRISockets := []string{}
199
200 for _, socket := range knownCRISockets {
201 if isSocket(socket) {
202 foundCRISockets = append(foundCRISockets, socket)
203 }
204 }
205
206 switch len(foundCRISockets) {
207 case 0:
208
209 return constants.DefaultCRISocket, nil
210 case 1:
211
212 return foundCRISockets[0], nil
213 default:
214
215 return "", errors.Errorf("Found multiple CRI endpoints on the host. Please define which one do you wish "+
216 "to use by setting the 'criSocket' field in the kubeadm configuration file: %s",
217 strings.Join(foundCRISockets, ", "))
218 }
219 }
220
221
222 func DetectCRISocket() (string, error) {
223 return detectCRISocketImpl(isExistingSocket, defaultKnownCRISockets)
224 }
225
226
227 func (runtime *CRIRuntime) SandboxImage() (string, error) {
228 args := []string{"-D=false", "info", "-o", "go-template", "--template", "{{.config.sandboxImage}}"}
229 out, err := runtime.crictl(args...).CombinedOutput()
230 if err != nil {
231 return "", errors.Wrapf(err, "output: %s, error", string(out))
232 }
233
234 sandboxImage := strings.TrimSpace(string(out))
235 if len(sandboxImage) > 0 {
236 return sandboxImage, nil
237 }
238
239 return "", errors.Errorf("the detected sandbox image is empty")
240 }
241
View as plain text