# Kubernetes Service и Ingress - сетевая публикация подов _Контейнеры (бонус) · LinuxLab Knowledge Base_ **TL;DR:** Service - стабильный VIP перед группой подов (label selector). Типы: ClusterIP (внутри), NodePort (порт на каждой ноде), LoadBalancer (внешний LB облака), ExternalName (CNAME). Ingress - L7 reverse-proxy (nginx/traefik) для HTTP-роутинга. ## Зачем Service Pod в k8s, эфемерный. Killed/rescheduled, новый IP. Если другой pod хочет с ним общаться, по IP, гонка. Service, это **стабильный VIP** (ClusterIP) и **DNS-имя** (`my-svc.my-ns.svc.cluster.local`), за которым стоит динамическая группа подов, выбранных по label selector'у. ```yaml apiVersion: v1 kind: Service metadata: name: web spec: selector: app: web # выбрать поды с этой label ports: - port: 80 # порт самого Service targetPort: 8080 # порт в pod'е type: ClusterIP # default ``` Под капотом control-plane (kube-controller-manager) поддерживает `Endpoints` (или `EndpointSlice` с k8s 1.21+), список IP:port всех живых подов, прошедших readinessProbe. Это «живой DNS» Service. ## Типы Service | Тип | Где доступен | Как работает | |-----|--------------|--------------| | **ClusterIP** | внутри кластера | VIP в service-CIDR, не маршрутится наружу | | **NodePort** | `:<30000-32767>` | open-port на каждой ноде, форвардит на ClusterIP | | **LoadBalancer** | внешний IP облачного LB | NodePort + cloud-controller вешает LB перед нодами | | **ExternalName** | DNS CNAME | Service резолвится в внешнее DNS-имя, без VIP | | **Headless** (`clusterIP: None`) | DNS A-records на pod IPs | DNS возвращает все pod IPs, балансирует клиент (StatefulSet, БД-кластеры) | **NodePort** в проде используют редко, некрасивые порты, не HTTPS. Обычно либо **LoadBalancer** (в облаке), либо за **Ingress** (для HTTP). ## Headless Service ```yaml spec: clusterIP: None selector: { app: cassandra } ``` Полезен для **stateful**-кейсов где клиенту нужны конкретные pod'ы: Cassandra, Kafka, etcd. DNS-запрос отдаёт A-records на каждый pod, без балансировки на уровне kube-proxy. Часто пара с [[kubernetes-pod-lifecycle|StatefulSet]] (даёт стабильные имена `cassandra-0.cassandra.default.svc`). ## kube-proxy, кто реализует Service На каждой ноде живёт `kube-proxy` (DaemonSet или systemd-unit). Его задача, обеспечить, чтобы пакет на ClusterIP/NodePort долетел до одного из pod'ов из Endpoints. Делает это **в data plane**, без user-space-проксирования. Три режима: | Режим | Что использует | Performance | Где default | |-------|----------------|-------------|-------------| | **iptables** | DNAT-цепочки [cmd-iptables](/kb/cmd-iptables.md) | O(N) на правило | большинство кластеров | | **ipvs** | LVS (Linux Virtual Server, kernel L4 LB) | O(1), hash-based | EKS большие кластеры, kops | | **nftables** | [cmd-nft](/kb/cmd-nft.md) (k8s 1.31+) | как iptables, новее | новый backend kube-proxy | При **iptables** на каждый Service-port создаётся chain типа `KUBE-SVC-XXX`, в нём random-DNAT (statistic mode random) на один из endpoints (`KUBE-SEP-YYY`). На 5000+ Service'ов начинаются тормоза, каждое iptables-update пересобирает всю таблицу. **ipvs** масштабируется лучше: правила в hash-table, не в linear-chain. Включается флагом kube-proxy `--proxy-mode=ipvs`. Требует kernel-модулей `ip_vs`, `ip_vs_rr`, `nf_conntrack`. Проверка режима: ```bash kubectl logs -n kube-system kube-proxy-xxxx | grep "Using" # или kubectl get cm -n kube-system kube-proxy -o yaml | grep mode ``` ## ExternalTrafficPolicy У NodePort/LoadBalancer есть важный параметр: - **`Cluster`** (default), пакет может пойти на pod на ЛЮБОЙ ноде, через SNAT. Source IP теряется, balancing equal. - **`Local`**, только на pod'ы текущей ноды. Source IP сохраняется, но если на ноде нет pod'а, пакет дропается. Обычно используют с healthcheck облачного LB чтобы он перестал слать на пустые ноды. Для серверов где нужен **client IP** (rate-limit, ban-list, geo) всегда `Local`. И добавить podAntiAffinity или DaemonSet чтобы pod'ы были на каждой ноде. ## Ingress, L7-роутинг Service работает на L4 (TCP/UDP). Для HTTP-приложений нужен reverse-proxy с роутингом по host/path, TLS-termination и т.д. Это **Ingress**. ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx tls: - hosts: [api.example.com] secretName: api-tls # k8s Secret с cert+key rules: - host: api.example.com http: paths: - path: /v1 pathType: Prefix backend: service: name: api-v1 port: { number: 80 } - path: /v2 pathType: Prefix backend: service: { name: api-v2, port: { number: 80 } } ``` Сам по себе Ingress, это **просто декларация**. Чтобы что-то заработало, нужен **Ingress Controller**, pod, который читает Ingress-объекты и конфигурирует реальный proxy. ## Сравнение Ingress Controllers | Controller | Под капотом | Сильные стороны | Слабые | |------------|-------------|-----------------|--------| | **ingress-nginx** | nginx + Lua | де-факто стандарт, огромная база annotations | reload при каждом изменении, не для тысяч хостов | | **traefik** | Go | автоматический Let's Encrypt, K8s CRDs (IngressRoute) | свой DSL, разная семантика с annotations | | **HAProxy ingress** | HAProxy | максимальный throughput, runtime-API без reload | меньше комьюнити | | **Envoy-based** (Contour, Emissary) | Envoy | xDS, HTTP/2/gRPC first-class | сложнее, больше ресурсов | | **cloud-native** (AWS ALB, GKE GCLB) | external LB | интеграция с облаком | vendor lock-in | Для большинства команд, `ingress-nginx`. Если нужен auto-TLS без cert-manager, `traefik`. Для high-throughput API-gateway Envoy-based (Contour/Gloo/Istio). ## Gateway API, следующее поколение В k8s 1.25+ стандартизирован **Gateway API**, наследник Ingress с разделением ответственности: - **GatewayClass** (cluster-admin задаёт implementation: nginx/envoy/...) - **Gateway** (infra-team: «вот listener на 443, TLS, ACL») - **HTTPRoute / TCPRoute** (app-team: «route /api на этот Service») Implementation: Istio, Contour, Cilium Service Mesh, kong. Ingress остаётся stable и поддерживается, но новые фичи (header modification, traffic split) идут в Gateway API. ## Service Mesh, когда Ingress уже мало Когда нужно **между сервисами**: mTLS, retry, circuit-break, observability, это уже не Service/Ingress, а service-mesh (Istio, Linkerd, Cilium). Sidecar Envoy/proxy в каждом pod'е плюс control-plane. Платишь complexity и CPU, получаешь zero-trust + tracing. ## Когда что-то пошло не так - **Service есть, pod'ы есть, но трафика нет** `kubectl get endpoints my-svc`. Если пусто, **selector не матчит** (опечатка в label) или pod не прошёл readinessProbe. - **`connection refused` изнутри кластера**, pod слушает на `127.0.0.1`, не на `0.0.0.0`. Service шлёт на pod IP, не localhost. - **NodePort недоступен снаружи**, host firewall (firewalld, iptables INPUT chain) дропает 30000-32767. Нужно разрешить. - **Ingress 503 Service Temporarily Unavailable**, Endpoints пусты или backend Service неправильный (другой namespace, опечатка). Лог nginx-controller в `kubectl logs -n ingress-nginx ...`. - **TLS cert mismatch**, secretName в Ingress есть, но Secret другого типа (надо `kubernetes.io/tls`) или хост в SAN не совпадает. - **Source IP всегда node IP**, `externalTrafficPolicy: Cluster`, включи `Local` или возьми X-Forwarded-For/PROXY-protocol на Ingress-уровне. - **kube-proxy iptables CPU 100%**, кластер 1000+ Service'ов, переключай на `ipvs`. - **Кросс-namespace Service не резолвится**, короткое имя `my-svc` работает только в своём namespace; используй FQDN `my-svc.other-ns.svc.cluster.local`. ## Полезные диагностические команды - `kubectl get svc -A`, все Service в кластере - `kubectl describe svc my-svc`, selector, endpoints, ports - `kubectl get endpoints my-svc`, реальные pod IPs за Service - `kubectl get endpointslice -l kubernetes.io/service-name=my-svc` - `kubectl run -it --rm debug --image=nicolaka/netshoot --`, pod с curl/dig/tcpdump для тестов изнутри кластера ## Команды ```bash kubectl expose deploy web --port=80 --target-port=8080 ``` Быстро создать ClusterIP Service для деплоймента - selector берётся из labels deploy ```bash kubectl get svc -o wide ``` Все Service с типом, ClusterIP, externalIP, портами и selector'ом ```bash kubectl get endpoints my-svc ``` Какие pod IP стоят за Service - быстрый sanity-check selector'а ```bash kubectl port-forward svc/my-svc 8080:80 ``` Локальный туннель к Service в обход Ingress - для отладки ```bash kubectl get ingress -A ``` Все Ingress объекты в кластере с host/path/backend ```bash kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=100 ``` Логи ingress-nginx - первое место смотреть при 502/503 ```bash kubectl exec -it some-pod -- nslookup my-svc.my-ns.svc.cluster.local ``` Проверить что DNS резолвится изнутри pod'а - частая причина 'не работает' ## См. также - [Kubernetes pod lifecycle - от Pending до Terminated](/kb/kubernetes-pod-lifecycle.md) - [CNI plugins - сеть Kubernetes (calico, cilium, flannel)](/kb/cni-plugins.md) - [gRPC - HTTP/2 + Protobuf RPC framework](/kb/grpc-basics.md) - [NAT и masquerade](/kb/nat.md) - [iptables - правила netfilter (legacy)](/kb/cmd-iptables.md) - [Helm charts - пакетный менеджер для Kubernetes](/kb/helm-charts.md) - [Service discovery в Prometheus: k8s, Consul, file_sd, relabel](/kb/service-discovery-prometheus.md)