原创 吴就业 205 0 2023-07-15
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/fa8546a6ecee40d6bb0575973546d25b
作者:吴就业
链接:https://wujiuye.com/article/fa8546a6ecee40d6bb0575973546d25b
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
kubectl logs -p
命令并不能获取已经删除的pod的容器日记,只适用于pod只是重启还未被删除的情况,能够获取重启前的容器的日记。
在Job场景,如果Job达到backoffLimit还是失败,而且backoffLimit值很小,很快就重试完,没能及时的获取到容器的日记。而达到backoffLimit后,job的controller会把pod删掉,这种情况就没办法获取pod的日记了,导致无法得知job执行失败的真正原因,只能看到job给的错误:”Job has reached the specified backoff limit”。
一种方案就是添加一个controller(operator),监听terraform job创建的pod,然后给pod添加finalizers,然后监听job的删除,在真正删除之前先获取pod的容器的日记,将日记通过configmap保存下来,或者发送event。
有一个缺陷是修改不了Job的状态,因为在job的controller删除pod之后,会更新覆盖job的状态。
这里以我们实际遇到的问题为案例,就是修改terraform apply、terraform destroy的job的backoffLimit为0后,我们无法获取真正terraform执行失败的原因。因此这个案例就是为了能够获取terraform-executor容器的日记。
由于terraform-controller会不断刷新configuration资源的状态,我们无法修改configuration资源的状态,因为一修改就会被terraform-controller覆盖。所以无法直接在kubevela上看到组件真正的失败原因。
需要注意的是,controller不要监听所有的pod。此案例中,我们应该只关心terraform的job创建的pod,因此controller需要添加事件过滤。
func (r *TerraformJobPodFailLogReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1.Pod{}).
WithEventFilter(&predicate.Funcs{
CreateFunc: func(createEvent event.CreateEvent) bool {
if !r.filterTerraformJobPod(createEvent.Object) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "create", "name",
createEvent.Object.GetName(), "namespace", createEvent.Object.GetNamespace())
return true
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
if !r.filterTerraformJobPod(deleteEvent.Object) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "delete", "name",
deleteEvent.Object.GetName(), "namespace", deleteEvent.Object.GetNamespace())
return true
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
if !r.filterTerraformJobPod(updateEvent.ObjectNew) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "update", "name",
updateEvent.ObjectNew.GetName(), "namespace", updateEvent.ObjectNew.GetNamespace())
return true
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return false
},
}).
Complete(r)
}
func (r *TerraformJobPodFailLogReconciler) filterTerraformJobPod(eventObj client.Object) bool {
pod, ok := eventObj.(*v1.Pod)
if !ok {
return false
}
jobName, ok := pod.Labels["job-name"]
if !ok || jobName == "" {
return false
}
job := &v12.Job{}
if err := r.Get(context.TODO(), types.NamespacedName{Namespace: pod.Namespace, Name: jobName}, job); err != nil {
return false
}
if !k8sutil.IsTerraformJob(job) {
return false
}
return true
}
获取pod的terraform-executor的容器日记。
func NewClientSet() (*kubernetes.Clientset, error) {
localK8sConfig := os.Getenv("KUBECONFIG")
var restCfg *rest.Config
var err error
if localK8sConfig != "" {
if restCfg, err = clientcmd.BuildConfigFromFlags("", localK8sConfig); err != nil {
return nil, err
}
} else if restCfg, err = rest.InClusterConfig(); err != nil {
return nil, err
}
return kubernetes.NewForConfig(restCfg)
}
func GetPodLog(podNamespaceName types.NamespacedName) (string, error) {
clientset, err := NewClientSet()
if err != nil {
return "", err
}
reader, err := clientset.CoreV1().Pods(podNamespaceName.Namespace).GetLogs(podNamespaceName.Name,
&v1.PodLogOptions{Container: "terraform-executor"}).Stream(context.TODO())
if err != nil {
return "", err
}
if podLog, err := io.ReadAll(reader); err != nil {
return "", err
} else {
return string(podLog), nil
}
}
此案例我们选择将获取的pod的日记发送event,关联到job,通过event查看错误日记。完整的controller代码如下。
type TerraformJobPodFailLogReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
}
func (r *TerraformJobPodFailLogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
pod := &v1.Pod{}
if err := r.Get(ctx, req.NamespacedName, pod); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
finalizer := "finalizers.kubernetes.io/pod-log.terraform-daemon"
if !pod.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(pod, finalizer) {
r.sendEvent(ctx, pod)
controllerutil.RemoveFinalizer(pod, finalizer)
return r.updateResource(ctx, pod)
}
return ctrl.Result{}, nil
}
if !controllerutil.ContainsFinalizer(pod, finalizer) {
controllerutil.AddFinalizer(pod, finalizer)
return r.updateResource(ctx, pod)
}
return ctrl.Result{}, nil
}
func (r *TerraformJobPodFailLogReconciler) updateResource(ctx context.Context, resource client.Object) (ctrl.Result, error) {
if err := r.Update(ctx, resource); err != nil {
if client.IgnoreNotFound(err) == nil {
return ctrl.Result{}, nil
}
if strings.Contains(err.Error(), "the object has been modified") {
return ctrl.Result{Requeue: true, RequeueAfter: 3 * time.Second}, nil
}
return ctrl.Result{}, client.IgnoreNotFound(err)
}
return ctrl.Result{}, nil
}
func (r *TerraformJobPodFailLogReconciler) sendEvent(ctx context.Context, pod *v1.Pod) {
if pod.Status.Phase == v1.PodSucceeded {
return
}
job := &v12.Job{}
if err := r.Get(ctx, types.NamespacedName{Namespace: pod.Namespace, Name: getJobName(pod)}, job); err != nil {
return
}
// 获取pod日记
podLog, err := k8sutil.GetPodLog(types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name})
if err != nil {
log.FromContext(ctx).Info(fmt.Sprintf("job: %s pod: %s container: %s get pod log error: %s", getJobName(pod), pod.Name, "terraform-executor", err.Error()))
return
}
// 替换掉特殊字符
podLog = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(podLog,
"[32m+", ""),
"[0m", ""),
"[1m", ""),
"[31m", ""),
"\u001B", "")
// 发送事件
r.Recorder.Event(job, "Warning", "FailedJobExec", podLog)
log.FromContext(ctx).Info(fmt.Sprintf("success to send event by job %s/%s", job.Namespace, job.Name))
}
func (r *TerraformJobPodFailLogReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1.Pod{}).
WithEventFilter(&predicate.Funcs{
CreateFunc: func(createEvent event.CreateEvent) bool {
if !r.filterTerraformJobPod(createEvent.Object) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "create", "name",
createEvent.Object.GetName(), "namespace", createEvent.Object.GetNamespace())
return true
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
if !r.filterTerraformJobPod(deleteEvent.Object) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "delete", "name",
deleteEvent.Object.GetName(), "namespace", deleteEvent.Object.GetNamespace())
return true
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
if !r.filterTerraformJobPod(updateEvent.ObjectNew) {
return false
}
log.Log.Info("consume terraform-job-pod event. ", "event", "update", "name",
updateEvent.ObjectNew.GetName(), "namespace", updateEvent.ObjectNew.GetNamespace())
return true
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return false
},
}).
Complete(r)
}
func (r *TerraformJobPodFailLogReconciler) filterTerraformJobPod(eventObj client.Object) bool {
pod, ok := eventObj.(*v1.Pod)
if !ok {
return false
}
jobName, ok := pod.Labels["job-name"]
if !ok || jobName == "" {
return false
}
job := &v12.Job{}
if err := r.Get(context.TODO(), types.NamespacedName{Namespace: pod.Namespace, Name: jobName}, job); err != nil {
return false
}
if !k8sutil.IsTerraformJob(job) {
return false
}
return true
}
func getJobName(pod *v1.Pod) string {
return pod.Labels["job-name"]
}
效果如下:
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
Go sdk本地开发调试sdk依赖问题;关于复杂嵌套结构体的schema声明;状态死循环监听,以及terraform命令终止时如何终止死循环;资源创建接口的默认可选字段不填遇到的坑;HCL代码输入变量的复杂校验。
很多企业内部为了不与云厂商绑定,避免上云容易下云难的尴尬,以及企业内部可能也会做私有云,或者封装一个混合云平台,因此不能直接用云厂商提供的provider。
通常申请基础设施,我们需要向运维描述我们需要什么基础设施、什么规格,运维根据我们的描述去检查是否已经申请过这样的资源,有就会直接给我们使用基础设施的信息,没有再帮我们申请,然后告诉我们使用基础设施的信息,例如mysql的jdbc和用户名、密码。如果将描述代码化,基础设施的申请自动化,就能实现“基础设施即代码”。而terraform就是实现“将描述代码化”的工具软件。
kubebuilder使用helm代替kustomize;代码改了但似乎没生效-镜像拉取问题; 使用ConfigMap替代Apollo配置中心的最少改动方案;环境变量的注入以及传递;Kubebuilder单测跑不起来;Helm chart和finalizer特性冲突问题。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。