feat: 添加istio策略下发服务#3827
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new istio-policy-controller component to automatically manage Istio DestinationRule and VirtualService resources based on Kubernetes Service events and a YAML configuration, and wires it into the build and runtime packaging.
Changes:
- Adds a controller-runtime based
istio-policy-controllerbinary that watchesServiceresources and creates/updates/deletes corresponding Istio policies using configurable merge and cleanup behavior. - Introduces configuration, metrics scaffolding, and Go module dependencies required to drive traffic policy generation and integrate with Istio client-go.
- Provides container packaging (Dockerfile, start script) and a Makefile target to build and package the new controller.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
install/conf/bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/container-start.sh |
Startup script for the istio-policy-controller container, including optional config rendering and a (currently outdated) flag usage comment block. |
install/conf/bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/Dockerfile |
Defines the istio-policy-controller container image, installing envsubst and wiring in the binary and start script. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/main.go |
Entrypoint that parses flags, initializes logging/config, sets up the controller-runtime manager with Istio and cloud schemes, and registers the ServiceReconciler. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/internal/option/option.go |
Declares the YAML configuration schema and controller options, and implements loading/unmarshalling of the config file into structured types. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/internal/metric/metric.go |
Adds (currently unused) Prometheus client metrics definitions and helpers for reporting client request counts and latencies. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/go.mod |
Creates a dedicated Go module for istio-policy-controller with Kubernetes, Istio, controller-runtime, and BCS dependencies and replaces. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/controllers/util.go |
Provides helper constants, a Service event predicate, and reflection-based merging/empty-check utilities for manipulating Istio TrafficPolicy fields. |
bcs-runtime/bcs-k8s/bcs-component/istio-policy-controller/controllers/service_controller.go |
Implements the ServiceReconciler that reconciles Service resources to Istio DestinationRule and VirtualService objects, including create/update/cleanup logic driven by the loaded configuration. |
Makefile |
Adds an istio-policy target to build the istio-policy-controller binary and copy its configuration into the package layout. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * either express or implied. See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| // package xxx |
There was a problem hiding this comment.
The // package xxx placeholder comment here is outdated and does not describe the option package; consider removing it or replacing it with a brief package‑level description of what these options/config types are used for.
| // package xxx | |
| // Package option defines configuration structures and controller options used by the istio-policy-controller. |
| cliReqCounter = prometheus.NewCounterVec( | ||
| prometheus.CounterOpts{ | ||
| Namespace: "bcs_network", | ||
| Subsystem: "cloudnetcontroller", | ||
| Name: "cli_request_total", | ||
| Help: "total request counter as client", | ||
| }, |
There was a problem hiding this comment.
The Prometheus metrics use Subsystem: "cloudnetcontroller", which doesn’t match this component’s name and will make the exported metrics harder to discover and distinguish; consider updating the subsystem (and, if needed, metric names) to reflect istio-policy-controller.
| if svc.Name == name && svc.Namespace == namespace { | ||
| if svc.Setting.AutoGenerateVS { | ||
| _, err := sr.IstioClient.NetworkingV1().VirtualServices(namespace). | ||
| Create(ctx, vs, metav1.CreateOptions{}) | ||
| if err != nil { |
There was a problem hiding this comment.
Here you create a VirtualService when a matching per‑service config has AutoGenerateVS enabled, but createVs will also unconditionally try to create the same VirtualService again if Global.Setting.AutoGenerateVS is true, which will cause an AlreadyExists error on the second call. It would be safer to ensure only one create path runs (e.g. by giving per‑service settings precedence over global, or by checking whether a VirtualService was already created).
| exec /data/bcs/${module}/${module} $@ | ||
|
|
||
| # Usage of ./istio-policy-controller: | ||
| # -address string | ||
| # address for controller (default "127.0.0.1") | ||
| # -alsologtostderr | ||
| # log to standard error as well as files | ||
| # -cloud string | ||
| # cloud mode for bcs network controller (default "tencentcloud") | ||
| # -kubeconfig string | ||
| # Paths to a kubeconfig. Only required if out-of-cluster. | ||
| # -log_backtrace_at string | ||
| # when logging hits line file:N, emit a stack trace | ||
| # -log_dir string | ||
| # If non-empty, write log files in this directory (default "./logs") | ||
| # -log_max_num int | ||
| # Max num of log file. (default 10) | ||
| # -log_max_size uint | ||
| # Max size (MB) per log file. (default 500) | ||
| # -logtostderr | ||
| # log to standard error instead of files | ||
| # -master --kubeconfig | ||
| # (Deprecated: switch to --kubeconfig) The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster. | ||
| # -metric_port int | ||
| # metric port for controller (default 8081) | ||
| # -port int | ||
| # por for controller (default 8080) | ||
| # -stderrthreshold string | ||
| # logs at or above this threshold go to stderr (default "2") | ||
| # -v int | ||
| # log level for V logs | ||
| # -vmodule string | ||
| # comma-separated list of pattern=N settings for file-filtered logging No newline at end of file |
There was a problem hiding this comment.
The flag usage block here appears to be copied from another component: several flags listed (e.g. -cloud, -kubeconfig, -port) are not actually defined in main.go, and one description even has a typo (por for controller). This can confuse operators trying to start or debug the binary; consider updating the usage comments to reflect the real flag set or removing this block if it’s not maintained.
| exec /data/bcs/${module}/${module} $@ | |
| # Usage of ./istio-policy-controller: | |
| # -address string | |
| # address for controller (default "127.0.0.1") | |
| # -alsologtostderr | |
| # log to standard error as well as files | |
| # -cloud string | |
| # cloud mode for bcs network controller (default "tencentcloud") | |
| # -kubeconfig string | |
| # Paths to a kubeconfig. Only required if out-of-cluster. | |
| # -log_backtrace_at string | |
| # when logging hits line file:N, emit a stack trace | |
| # -log_dir string | |
| # If non-empty, write log files in this directory (default "./logs") | |
| # -log_max_num int | |
| # Max num of log file. (default 10) | |
| # -log_max_size uint | |
| # Max size (MB) per log file. (default 500) | |
| # -logtostderr | |
| # log to standard error instead of files | |
| # -master --kubeconfig | |
| # (Deprecated: switch to --kubeconfig) The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster. | |
| # -metric_port int | |
| # metric port for controller (default 8081) | |
| # -port int | |
| # por for controller (default 8080) | |
| # -stderrthreshold string | |
| # logs at or above this threshold go to stderr (default "2") | |
| # -v int | |
| # log level for V logs | |
| # -vmodule string | |
| # comma-separated list of pattern=N settings for file-filtered logging | |
| exec /data/bcs/${module}/${module} $@ |
| * either express or implied. See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| // package xxx |
There was a problem hiding this comment.
The // package xxx placeholder comment no longer matches the actual package name and can be misleading; it’s better to remove or replace it with a meaningful description of this binary.
| // package xxx | |
| // istio-policy-controller is the entry point for the Istio policy controller binary. |
| return err | ||
| } | ||
|
|
||
| ctrl.Log.WithName("config").Info(fmt.Sprintf("config content: %s", string(content))) |
There was a problem hiding this comment.
InitCfg logs the entire raw configuration file content, which can bloat logs and may inadvertently expose sensitive data if secrets are ever added to this config; it would be safer to either omit logging the full file or log only high‑level, non‑sensitive fields.
| ctrl.Log.WithName("config").Info(fmt.Sprintf("config content: %s", string(content))) | |
| ctrl.Log.WithName("config").Info(fmt.Sprintf("loaded config from %s", o.ConfigPath)) |
| dr.Spec.TrafficPolicy = tp | ||
|
|
||
| if isEmptyStruct(dr.Spec.TrafficPolicy.LoadBalancer) { | ||
| dr.Spec.TrafficPolicy.LoadBalancer = nil | ||
| } |
There was a problem hiding this comment.
dr.Spec.TrafficPolicy is set directly to tp, which can be nil when neither a service‑level nor a global TrafficPolicy is configured; the subsequent field accesses (e.g. dr.Spec.TrafficPolicy.LoadBalancer) will then panic with a nil pointer dereference. Consider keeping a non‑nil empty TrafficPolicy when no policy is provided, or guarding these field clean‑up calls with a dr.Spec.TrafficPolicy != nil check.
| func init() { | ||
| metrics.Registry.MustRegister(cliReqCounter) | ||
| metrics.Registry.MustRegister(cliRespSummary) | ||
| } | ||
|
|
||
| // StatClientRequest report client request metrics | ||
| func StatClientRequest(module, rpc string, respCode int, result string, inTime, outTime time.Time) { |
There was a problem hiding this comment.
StatClientRequest and the associated Prometheus vectors are defined but not used anywhere in this module, which may indicate dead code or an incomplete metrics integration; consider either using these metrics in the controller or removing them until they are needed.
| if client.IgnoreNotFound(err) != nil { | ||
| sr.Log.Error(err, "failed to get DestinationRule") | ||
| } | ||
| } | ||
|
|
||
| vs, err := sr.IstioClient.NetworkingV1().VirtualServices(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
| if err != nil { | ||
| if client.IgnoreNotFound(err) != nil { | ||
| sr.Log.Error(err, "failed to get VirtualService") |
There was a problem hiding this comment.
In this branch, non‑NotFound errors returned when fetching the DestinationRule are only logged and then ignored, so the reconciler never retries deletion and may leave policies orphaned if the API call failed transiently. It would be more robust to return the error (or a combined error) so that the controller can requeue and attempt cleanup again.
| if client.IgnoreNotFound(err) != nil { | |
| sr.Log.Error(err, "failed to get DestinationRule") | |
| } | |
| } | |
| vs, err := sr.IstioClient.NetworkingV1().VirtualServices(namespace).Get(ctx, name, metav1.GetOptions{}) | |
| if err != nil { | |
| if client.IgnoreNotFound(err) != nil { | |
| sr.Log.Error(err, "failed to get VirtualService") | |
| if ignErr := client.IgnoreNotFound(err); ignErr != nil { | |
| sr.Log.Error(ignErr, "failed to get DestinationRule") | |
| return ignErr | |
| } | |
| } | |
| vs, err := sr.IstioClient.NetworkingV1().VirtualServices(namespace).Get(ctx, name, metav1.GetOptions{}) | |
| if err != nil { | |
| if ignErr := client.IgnoreNotFound(err); ignErr != nil { | |
| sr.Log.Error(ignErr, "failed to get VirtualService") | |
| return ignErr |
| vs, err := sr.IstioClient.NetworkingV1().VirtualServices(namespace).Get(ctx, name, metav1.GetOptions{}) | ||
| if err != nil { | ||
| if client.IgnoreNotFound(err) != nil { | ||
| sr.Log.Error(err, "failed to get VirtualService") | ||
| } |
There was a problem hiding this comment.
Similarly to the DestinationRule fetch above, non‑NotFound errors when getting the VirtualService are logged but not returned, so transient API failures can prevent policy cleanup without a retry. Consider propagating this error as well (for example via errors.Join) so deletion can be retried by the reconciliation loop.
| }, | ||
| }, | ||
| Spec: v1alpha3.DestinationRule{ | ||
| Host: fmt.Sprintf("%s.%s.svc.cluster.local", name, namespace), |
There was a problem hiding this comment.
这个svc.cluster.local 多次用到拼接,可以弄一个封装的方法
添加istio策略下发服务