1
16
17 package lifecycle
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25 "net/url"
26 "strconv"
27 "strings"
28 "time"
29
30 v1 "k8s.io/api/core/v1"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/apimachinery/pkg/util/intstr"
33 utilfeature "k8s.io/apiserver/pkg/util/feature"
34 "k8s.io/client-go/tools/record"
35 "k8s.io/klog/v2"
36 "k8s.io/kubernetes/pkg/features"
37 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
38 "k8s.io/kubernetes/pkg/kubelet/metrics"
39 kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
40 "k8s.io/kubernetes/pkg/kubelet/util/format"
41 httpprobe "k8s.io/kubernetes/pkg/probe/http"
42 "k8s.io/kubernetes/pkg/security/apparmor"
43 )
44
45 const (
46 maxRespBodyLength = 10 * 1 << 10
47 )
48
49 type handlerRunner struct {
50 httpDoer kubetypes.HTTPDoer
51 commandRunner kubecontainer.CommandRunner
52 containerManager podStatusProvider
53 eventRecorder record.EventRecorder
54 }
55
56 type podStatusProvider interface {
57 GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error)
58 }
59
60
61 func NewHandlerRunner(httpDoer kubetypes.HTTPDoer, commandRunner kubecontainer.CommandRunner, containerManager podStatusProvider, eventRecorder record.EventRecorder) kubecontainer.HandlerRunner {
62 return &handlerRunner{
63 httpDoer: httpDoer,
64 commandRunner: commandRunner,
65 containerManager: containerManager,
66 eventRecorder: eventRecorder,
67 }
68 }
69
70 func (hr *handlerRunner) Run(ctx context.Context, containerID kubecontainer.ContainerID, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler) (string, error) {
71 switch {
72 case handler.Exec != nil:
73 var msg string
74
75 output, err := hr.commandRunner.RunInContainer(ctx, containerID, handler.Exec.Command, 0)
76 if err != nil {
77 msg = fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, string(output))
78 klog.V(1).ErrorS(err, "Exec lifecycle hook for Container in Pod failed", "execCommand", handler.Exec.Command, "containerName", container.Name, "pod", klog.KObj(pod), "message", string(output))
79 }
80 return msg, err
81 case handler.HTTPGet != nil:
82 err := hr.runHTTPHandler(ctx, pod, container, handler, hr.eventRecorder)
83 var msg string
84 if err != nil {
85 msg = fmt.Sprintf("HTTP lifecycle hook (%s) for Container %q in Pod %q failed - error: %v", handler.HTTPGet.Path, container.Name, format.Pod(pod), err)
86 klog.V(1).ErrorS(err, "HTTP lifecycle hook for Container in Pod failed", "path", handler.HTTPGet.Path, "containerName", container.Name, "pod", klog.KObj(pod))
87 }
88 return msg, err
89 case handler.Sleep != nil:
90 err := hr.runSleepHandler(ctx, handler.Sleep.Seconds)
91 var msg string
92 if err != nil {
93 msg = fmt.Sprintf("Sleep lifecycle hook (%d) for Container %q in Pod %q failed - error: %v", handler.Sleep.Seconds, container.Name, format.Pod(pod), err)
94 klog.V(1).ErrorS(err, "Sleep lifecycle hook for Container in Pod failed", "sleepSeconds", handler.Sleep.Seconds, "containerName", container.Name, "pod", klog.KObj(pod))
95 }
96 return msg, err
97 default:
98 err := fmt.Errorf("invalid handler: %v", handler)
99 msg := fmt.Sprintf("Cannot run handler: %v", err)
100 klog.ErrorS(err, "Cannot run handler")
101 return msg, err
102 }
103 }
104
105
106
107
108
109
110
111 func resolvePort(portReference intstr.IntOrString, container *v1.Container) (int, error) {
112 if portReference.Type == intstr.Int {
113 return portReference.IntValue(), nil
114 }
115 portName := portReference.StrVal
116 port, err := strconv.Atoi(portName)
117 if err == nil {
118 return port, nil
119 }
120 for _, portSpec := range container.Ports {
121 if portSpec.Name == portName {
122 return int(portSpec.ContainerPort), nil
123 }
124 }
125 return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container)
126 }
127
128 func (hr *handlerRunner) runSleepHandler(ctx context.Context, seconds int64) error {
129 if !utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepAction) {
130 return nil
131 }
132 c := time.After(time.Duration(seconds) * time.Second)
133 select {
134 case <-ctx.Done():
135
136 metrics.LifecycleHandlerSleepTerminated.Inc()
137 return fmt.Errorf("container terminated before sleep hook finished")
138 case <-c:
139 return nil
140 }
141 }
142
143 func (hr *handlerRunner) runHTTPHandler(ctx context.Context, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler, eventRecorder record.EventRecorder) error {
144 host := handler.HTTPGet.Host
145 podIP := host
146 if len(host) == 0 {
147 status, err := hr.containerManager.GetPodStatus(ctx, pod.UID, pod.Name, pod.Namespace)
148 if err != nil {
149 klog.ErrorS(err, "Unable to get pod info, event handlers may be invalid.", "pod", klog.KObj(pod))
150 return err
151 }
152 if len(status.IPs) == 0 {
153 return fmt.Errorf("failed to find networking container: %v", status)
154 }
155 host = status.IPs[0]
156 podIP = host
157 }
158
159 req, err := httpprobe.NewRequestForHTTPGetAction(handler.HTTPGet, container, podIP, "lifecycle")
160 if err != nil {
161 return err
162 }
163 resp, err := hr.httpDoer.Do(req)
164 discardHTTPRespBody(resp)
165
166 if isHTTPResponseError(err) {
167 klog.V(1).ErrorS(err, "HTTPS request to lifecycle hook got HTTP response, retrying with HTTP.", "pod", klog.KObj(pod), "host", req.URL.Host)
168
169 req := req.Clone(context.Background())
170 req.URL.Scheme = "http"
171 req.Header.Del("Authorization")
172 resp, httpErr := hr.httpDoer.Do(req)
173
174
175 if httpErr == nil {
176 metrics.LifecycleHandlerHTTPFallbacks.Inc()
177 if eventRecorder != nil {
178
179 eventRecorder.Event(pod, v1.EventTypeWarning, "LifecycleHTTPFallback", fmt.Sprintf("request to HTTPS lifecycle hook %s got HTTP response, retry with HTTP succeeded", req.URL.Host))
180 }
181 err = nil
182 }
183 discardHTTPRespBody(resp)
184 }
185 return err
186 }
187
188 func discardHTTPRespBody(resp *http.Response) {
189 if resp == nil {
190 return
191 }
192
193
194
195
196 defer resp.Body.Close()
197
198 if resp.ContentLength <= maxRespBodyLength {
199 io.Copy(io.Discard, &io.LimitedReader{R: resp.Body, N: maxRespBodyLength})
200 }
201 }
202
203
204
205 func NewAppArmorAdmitHandler(validator apparmor.Validator) PodAdmitHandler {
206 return &appArmorAdmitHandler{
207 Validator: validator,
208 }
209 }
210
211 type appArmorAdmitHandler struct {
212 apparmor.Validator
213 }
214
215 func (a *appArmorAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult {
216
217 if attrs.Pod.Status.Phase != v1.PodPending {
218 return PodAdmitResult{Admit: true}
219 }
220
221 err := a.Validate(attrs.Pod)
222 if err == nil {
223 return PodAdmitResult{Admit: true}
224 }
225 return PodAdmitResult{
226 Admit: false,
227 Reason: "AppArmor",
228 Message: fmt.Sprintf("Cannot enforce AppArmor: %v", err),
229 }
230 }
231
232 func isHTTPResponseError(err error) bool {
233 if err == nil {
234 return false
235 }
236 urlErr := &url.Error{}
237 if !errors.As(err, &urlErr) {
238 return false
239 }
240 return strings.Contains(urlErr.Err.Error(), "server gave HTTP response to HTTPS client")
241 }
242
View as plain text