{"id":44771854,"url":"https://github.com/stone/k8s-node-external-ip-watcher","last_synced_at":"2026-02-16T05:34:56.055Z","repository":{"id":322390185,"uuid":"1089294806","full_name":"stone/k8s-node-external-ip-watcher","owner":"stone","description":"A lightweight Kubernetes event watcher (with a horrible name) that monitors node external IP changes and executes actions based on a templated configuration.","archived":false,"fork":false,"pushed_at":"2025-11-14T12:17:01.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-14T14:18:02.395Z","etag":null,"topics":["kubernetes"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-04T06:32:49.000Z","updated_at":"2025-11-14T12:15:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stone/k8s-node-external-ip-watcher","commit_stats":null,"previous_names":["stone/k8s-node-external-ip-watcher"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/stone/k8s-node-external-ip-watcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stone%2Fk8s-node-external-ip-watcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stone%2Fk8s-node-external-ip-watcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stone%2Fk8s-node-external-ip-watcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stone%2Fk8s-node-external-ip-watcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stone","download_url":"https://codeload.github.com/stone/k8s-node-external-ip-watcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stone%2Fk8s-node-external-ip-watcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29500832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-16T03:57:51.541Z","status":"ssl_error","status_checked_at":"2026-02-16T03:55:59.854Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kubernetes"],"created_at":"2026-02-16T05:34:55.375Z","updated_at":"2026-02-16T05:34:56.047Z","avatar_url":"https://github.com/stone.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"```\n _   _           _      ___ ____   __        __    _       _               \n| \\ | | ___   __| | ___|_ _|  _ \\  \\ \\      / /_ _| |_ ___| |__   ___ _ __ \n|  \\| |/ _ \\ / _` |/ _ \\| || |_) |  \\ \\ /\\ / / _` | __/ __| '_ \\ / _ \\ '__|\n| |\\  | (_) | (_| |  __/| ||  __/    \\ V  V / (_| | || (__| | | |  __/ |   \n|_| \\_|\\___/ \\__,_|\\___|___|_|        \\_/\\_/ \\__,_|\\__\\___|_| |_|\\___|_|   \n                                                                           \n```\n\n\nA lightweight Kubernetes event watcher that monitors node external IP\nchanges and executes actions based on a templated configuration.\n\n- Uses Kubernetes informers for optimal API usage\n- Hash-based checks prevents redundant executions\n- Go template rendering for output\n- Prevents accidental removal of all nodes with a minimum node count setting\n- Allows for additional static IPs in the output\n- Waits for Kubernetes informer cache to sync, full sync on startup\n\nWhat is monitored:\n\n- Node Added: New node joins the cluster\n- Node Updated: Node IP changes\n- Node Deleted: Node removed from cluster\n\nThe first use case is to update a external DNS loadbalancer configuration,\nwhen nodes are added/removed from the cluster. Needed when the cloud provider \ndoes not provice a UDP load balancer. (I'm looking at you, Scaleway!)\n\nBut it can be used for any use case where node external IPs need to\nbe monitored and acted upon.\n\n- Generate DNS records from node IPs \n- Update external firewall rules based on cluster topology\n- Synchronize external systems with Kubernetes node changes\n\n\n## Configuration\n\n### Config File (config.yaml)\n\n```yaml\n# Log level: debug, info, warn, error\nlogLevel: info\n\n# Path to kubeconfig file (optional)\nkubeConfig: /path/to/kubeconfig\n\n# Path to the Go template file\ntemplatePath: template.tmpl\n\n# Path where the rendered output will be written\noutputPath: /tmp/node-ips.conf\n\n# Command to execute after rendering\ncommand: /usr/local/bin/reload-config.sh\n\n# Static IPs to always include\nstaticIPs:\n  - \"192.168.1.100\"\n  - \"192.168.1.101\"\n\n# Resync interval in seconds\nresyncInterval: 300\n\n# Minimum node count (safety net)\nminNodeCount: 1\n```\n\n### Command-Line Flags\n\nFlags will override config file values:\n\n```bash\n./k8s-node-external-ip-watcher \\\n  --config config.yaml \\\n  --log-level debug \\\n  --kubeconfig ~/.kube/config \\\n  --template template.tmpl \\\n  --output /etc/nginx/backends.conf\n```\n\n## Template Format\n\nTemplates use Go's `text/template` package. Available data:\n\n```go\ntype NodeData struct {\n    Nodes     []NodeInfo  // Kubernetes nodes with external IPs\n    StaticIPs []string    // Static IPs from config\n    AllIPs    []string    // Combined list of all IPs\n    Timestamp time.Time   // When the template was rendered\n}\n\ntype NodeInfo struct {\n    Name       string  // Node name\n    ExternalIP string  // External IP address\n}\n```\n\n### Example Templates\n\n#### Simple IP List\n\n```\n{{- range .AllIPs }}\n{{ . }}\n{{- end }}\n```\n\n#### Nginx Upstream Configuration\n\n```\n# Generated: {{ .Timestamp.Format \"2006-01-02 15:04:05 MST\" }}\n\nupstream k8s_nodes {\n{{- range .Nodes }}\n    server {{ .ExternalIP }}:80;  # {{ .Name }}\n{{- end }}\n{{- range .StaticIPs }}\n    server {{ . }}:80;  # static\n{{- end }}\n}\n```\n\n#### Detailed Configuration\n\n```\n# Generated: {{ .Timestamp.Format \"2006-01-02 15:04:05 MST\" }}\n# Total IPs: {{ len .AllIPs }}\n\n# Kubernetes Nodes\n{{- range .Nodes }}\n# {{ .Name }}\nserver {{ .ExternalIP }};\n{{- end }}\n\n{{- if .StaticIPs }}\n# Static IPs\n{{- range .StaticIPs }}\nserver {{ . }};\n{{- end }}\n{{- end }}\n```\n\n## Examples\n\n### Varnish Backend configuration\n**config.yaml:**\n```yaml\nlogLevel: info\ntemplatePath: varnish-backend.tmpl\noutputPath: /etc/varnish/backends.vcl\ncommand: /usr/bin/varnishreload\nminNodeCount: 3\n```\n\n**varnish-backend.tmpl:**\n```backend k8s_nodes {\n{{- range .Nodes }}\n    .host = \"{{ .ExternalIP }}\";\n    .port = \"8080\";\n{{- end }}\n}\n```\n\n### Nginx Backend Updates\n\n**config.yaml:**\n```yaml\nlogLevel: info\ntemplatePath: nginx-backend.tmpl\noutputPath: /etc/nginx/conf.d/backends.conf\ncommand: /usr/local/bin/reload-nginx.sh\nminNodeCount: 2\n```\n\n**reload-nginx.sh:**\n```bash\n#!/bin/bash\n# Validate and reload nginx configuration\nnginx -t \u0026\u0026 nginx -s reload\n```\n\n**nginx-backend.tmpl:**\n```\nupstream k8s_backends {\n{{- range .Nodes }}\n    server {{ .ExternalIP }}:8080;\n{{- end }}\n}\n```\n\n### HAProxy Configuration\n\n**config.yaml:**\n```yaml\ntemplatePath: haproxy-backend.tmpl\noutputPath: /etc/haproxy/backends.cfg\ncommand: /usr/local/bin/reload-haproxy.sh\n```\n\n**reload-haproxy.sh:**\n```bash\n#!/bin/bash\n# Validate and reload haproxy configuration\nhaproxy -c -f /etc/haproxy/haproxy.cfg \u0026\u0026 systemctl reload haproxy\n```\n\n**haproxy-backend.tmpl:**\n```\nbackend k8s_nodes\n    balance roundrobin\n{{- range .Nodes }}\n    server {{ .Name }} {{ .ExternalIP }}:80 check\n{{- end }}\n```\n\n## Kubernetes RBAC Setup for k8s-node-external-ip-watcher\n\nThere are example RBAC manifests in the `k8s-manifests` directory.\nThese manifests create a ServiceAccount with minimal permissions required to\nmonitor Kubernetes node changes (add/update/delete events) and read node\nexternal IPs.\n\nApply the RBAC resources:\n\n```bash\nkubectl apply -f k8s-manifests/\n```\n\nThe `node-ip-watcher` ServiceAccount has only these permissions:\n\n- `get` - Read individual node details\n- `list` - List all nodes in the cluster\n- `watch` - Subscribe to node change events\n\nThese are the minimal permissions required for the application to function.\n\n\nCreate `kubeconfig` file using the `node-ip-watcher` ServiceAccount token and CA cert.\n```bash\nCLUSTER_NAME=$(kubectl config view -o jsonpath='{.clusters[0].name}')\nCLUSTER_SERVER=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}')\nCLUSTER_CA=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')\nCLUSTER_TOKEN=$(kubectl create token node-ip-watcher -n default --duration=87600h)\n\ncat \u003e kubeconfig \u003c\u003cEOF\napiVersion: v1\nkind: Config\nclusters:\n- cluster:\n    certificate-authority-data: ${CLUSTER_CA}\n    server: ${CLUSTER_SERVER}\n  name: ${CLUSTER_NAME}\ncontexts:\n- context:\n    cluster: ${CLUSTER_NAME}\n    user: node-ip-watcher\n  name: node-ip-watcher\ncurrent-context: node-ip-watcher\nusers:\n- name: node-ip-watcher\n  user:\n    token: ${CLUSTER_TOKEN}\nEOF\n```\n\nTests:\n```bash\nKUBECONFIG=kubeconfig kubectl auth can-i list nodes\nKUBECONFIG=kubeconfig kubectl auth can-i watch nodes\n# Should fail\nKUBECONFIG=kubeconfig kubectl auth can-i list pods\n```\n\nUpdate config.yaml to include the path to the generated kubeconfig.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstone%2Fk8s-node-external-ip-watcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstone%2Fk8s-node-external-ip-watcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstone%2Fk8s-node-external-ip-watcher/lists"}