如何在Makefile中实现任意目标执行后自动运行collect-data-point?
问题
我有一个包含多个target的Makefile,希望在调用任意target后自动运行名为collect-data-point的目标,且无需手动将其添加到数百个target中。将collect-data-point作为依赖的方式不可行,因为我需要捕获主target运行后才生成的值(比如退出状态码$?),因此必须在主target执行完成后再运行collect-data-point。
现有Target示例
# Wait for jobs and pods to be ready in a given namespace wait-resources: @. $(SUPPORT_SCRIPT); TIMEOUT=$(TIMEOUT) \ APP_NAME=$(APP_NAME) \ waitForResources $(NAMESPACE) $(HAS_OPERATOR_RESOURCES) $(FAIL_IF_JOB_MISSING) # Monitor all resources in the specified namespace monitor-resources: @echo "Monitoring resources: '$(NAMESPACE_RESOURCES)' in namespace '$(NAMESPACE)'" @watch kubectl get $(NAMESPACE_RESOURCES) -n $(NAMESPACE) # Retrieve resources from the specified namespace fetch-resources: @echo "Fetching resources: $(NAMESPACE_RESOURCES) from namespace '$(NAMESPACE)'" @kubectl get $(NAMESPACE_RESOURCES) -n $(NAMESPACE) # Detailed description of resources in a namespace filtered by an app label describe-app-resources: @. $(SUPPORT_SCRIPT); HELM_REPO=$(HELM_REPO) describeResources $(NAMESPACE) $(APP_NAME) # Display logs for pods in a namespace with a specific app label logs-app-pods: @. $(SUPPORT_SCRIPT); HELM_REPO=$(HELM_REPO) getPodLogs $(NAMESPACE) $(APP_NAME) # List pods in a specific namespace list-namespace-pods: kubectl get pods -n $(NAMESPACE) # List container images for a specific app within a namespace list-pod-images: kubectl get pods -l app=${APP_NAME} -n $(NAMESPACE) -o jsonpath="{range .items[*]}{.metadata.name}:{' '}{range .spec.containers[*]}{.image}{end}{'\n'}{end}{'\n'}" # Export the current KUBECONFIG as base64 export-kubeconfig: @KUBE_CONFIG_BASE64=`kubectl config view --flatten | base64`; \ echo "Encoded KUBE_CONFIG_BASE64: $$(echo $${KUBE_CONFIG_BASE64} | cut -c 1-40)..."; \ echo "Added to: RulesConfig.mak"; \ echo -e "\n\n# Encoded from: kubectl config view --flatten\nKUBE_CONFIG_BASE64 = $${KUBE_CONFIG_BASE64}" >> RulesConfig.mak
需自动执行的Metrics目标
define METRICS_TARGET_BODY {"CI_PIPELINE_ID":"$(CI_PIPELINE_ID)","CI_JOB_ID":"$(CI_JOB_ID)","MAKE_TARGET":"$(MAKECMDGOALS)","MAKE_VERSION":"$(MAKE_VERSION)","CI_PROJECT_ID":"$(CI_PROJECT_ID)","CI_PROJECT_PATH":"$(CI_PROJECT_PATH)","STATUS":"$$?"} endef export METRICS_TARGET_BODY collect-data-point: @curl -s -v -X POST $(DATA_ENDPOINT)/point \ -H "Content-Type: application/json" \ -d "$$(echo '$(METRICS_TARGET_BODY)' | sed 's/\"STATUS\":\"$$?\"/\"STATUS\":\"'$$?'\"/')" \ > /dev/null 2>&1 || true &
执行要求
- 运行
make fetch-resources时,先执行fetch-resources,完成后执行collect-data-point; - 运行
make fetch-resources monitor-resources时,先执行fetch-resources,完成后执行collect-data-point,再执行monitor-resources,完成后再次执行collect-data-point。
解决方案
可以通过Make的eval函数批量修改现有target,让每个目标执行完成后自动触发collect-data-point,无需手动修改每个target的定义。
步骤1:定义需要监控的Target列表
先把所有需要自动触发metrics的target整理到变量中,方便批量管理:
MONITORED_TARGETS := wait-resources monitor-resources fetch-resources describe-app-resources logs-app-pods list-namespace-pods list-pod-images export-kubeconfig
步骤2:用eval批量重定义Target
使用eval函数遍历MONITORED_TARGETS,为每个target创建私有副本并修改原target的执行逻辑:
# 遍历所有需要监控的target,批量重定义 $(foreach TARGET,$(MONITORED_TARGETS),\ $(eval $(TARGET): ; $$(MAKE) --no-print-directory _$(TARGET) && $$(MAKE) --no-print-directory collect-data-point)\ $(eval _$(TARGET): ; $(shell grep -A 100 "^$(TARGET):" Makefile | grep -v "^$(TARGET):" | grep -v "^_$(TARGET):" | grep -v "^$$(eval" | sed 's/^ //'))\ )
这段代码的作用:
- 为每个原target创建前缀为
_的私有目标(如_fetch-resources),完整保留原有的命令逻辑; - 重定义原target,使其先执行私有目标,执行完成后立即调用
collect-data-point。
步骤3:修正collect-data-point的状态码捕获
原collect-data-point的状态码替换逻辑可以简化,直接捕获上一步命令的退出码,避免复杂的sed替换:
define METRICS_TARGET_BODY {"CI_PIPELINE_ID":"$(CI_PIPELINE_ID)","CI_JOB_ID":"$(CI_JOB_ID)","MAKE_TARGET":"$(MAKECMDGOALS)","MAKE_VERSION":"$(MAKE_VERSION)","CI_PROJECT_ID":"$(CI_PROJECT_ID)","CI_PROJECT_PATH":"$(CI_PROJECT_PATH)","STATUS":"STATUS_PLACEHOLDER"} endef export METRICS_TARGET_BODY collect-data-point: @STATUS=$$?; \ curl -s -v -X POST $(DATA_ENDPOINT)/point \ -H "Content-Type: application/json" \ -d "$$(echo '$(METRICS_TARGET_BODY)' | sed 's/STATUS_PLACEHOLDER/'"$$STATUS"'/')" \ > /dev/null 2>&1 || true &
验证效果
- 执行
make fetch-resources:先运行_fetch-resources(原命令),完成后自动执行collect-data-point; - 执行
make fetch-resources monitor-resources:依次执行_fetch-resources→collect-data-point→_monitor-resources→collect-data-point,完全符合需求。
替代方案:使用ONESHELL和trap
如果你的Make版本支持.ONESHELL(GNU Make 3.82+),也可以用shell的trap命令在每个target执行结束后自动调用metrics,但这种方式需要手动修改每个target的开头:
.ONESHELL: SHELL := /bin/bash wait-resources: trap 'make collect-data-point' EXIT @. $(SUPPORT_SCRIPT); TIMEOUT=$(TIMEOUT) \ APP_NAME=$(APP_NAME) \ waitForResources $(NAMESPACE) $(HAS_OPERATOR_RESOURCES) $(FAIL_IF_JOB_MISSING)
该方案适合target数量较少的场景,target较多时推荐使用eval批量处理的方式。
内容的提问来源于stack exchange,提问作者Joao Lima
相关产品推荐
相关产品推荐

