Kubernetes Services
Pods get a new IP address every time they restart, so nothing can talk to them directly. A Service is the stable address that stays put.
What is it?#
A Service gives a group of pods one fixed IP address and DNS name. Clients talk to the Service; it forwards to whichever pods are currently healthy.
Why it exists#
Pod IPs are ephemeral by design. A pod that restarts, moves node, or scales gets a different address. Hard-coding one guarantees breakage.
A Service solves two problems at once: a stable address, and load balancing across whatever pods exist right now.
What it is made of#
Two things happen behind one object — and nearly all Service bugs are in the first while people debug the second:
The four types#
| Type | Gives you | Reachable from | Use when |
|---|---|---|---|
| ClusterIP (default) | Internal IP + DNS name | Inside the cluster only | Service-to-service. Most of the time |
| NodePort | A port on every node | Outside, if you can reach nodes | Rarely direct — it is a building block |
| LoadBalancer | A cloud load balancer | The internet | Cloud-managed external entry |
| ExternalName | A DNS CNAME, no proxying | Inside | Aliasing an external hostname |
How to use it#
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web # MUST match the pods' labels exactly
ports:
- port: 80 # the port the Service listens on
targetPort: 8080 # the port the CONTAINER listens onport vs targetPort — swapped constantlyportis what clients connect to on the Service.targetPortis the port inside the container.
They are often the same, which is why the distinction is easy to miss — and then one day they differ and you get "connection refused" from a perfectly healthy pod.
الرقمين دول بيتعكسوا على طول، فخد بالك:
port= دخول الـ Service. ده اللي العميل بيتصل عليه.targetPort= دخول الكونتينر. ده اللي التطبيق فعلاً سامع عليه.
وسبب إنها بتلخبط: إنهم في ٩٠٪ من الحالات بيبقوا نفس الرقم، فمحدش بياخد باله من الفرق. وبعدين يوم يجي يكونوا مختلفين، وتشوف connection refused من بود سليم ١٠٠٪ ومش فاهم إيه اللي بيحصل.
إزاي تتأكد؟ بصّ على البورت الحقيقي بتاع الكونتينر:
kubectl get pod <name> -o jsonpath='{.spec.containers[*].ports[*].containerPort}{"\n"}'ولازم يطابق الـ targetPort بتاع الـ Service.
DNS names#
Once a Service exists, it has a name:
| From | You can write |
|---|---|
| Same namespace | web |
| Another namespace | web.production |
| Anywhere (fully qualified) | web.production.svc.cluster.local |
So an app connects to postgres://db:5432 where db is just the Service name. You never handle IP addresses.
اسم الـ Service بيتحول لاسم DNS تلقائياً، والاسم مبني من ٤ حاجات:
وإنت مش لازم تكتب الاسم الطويل ده كله:
| إنت فين | تكتب إيه |
|---|---|
| نفس الـ namespace | web |
| namespace تانية | web.production |
| في أي مكان (الاسم الكامل) | web.production.svc.cluster.local |
يعني تطبيقك بيتصل على postgres://db:5432 وخلاص، و db هو اسم الـ Service. إنت عمرك ما بتلمس IP.
لما تكتب db بس، الـ resolver بيجرّب يوسّعها على كل الـ search domains واحدة واحدة: db.production.svc.cluster.local، وبعدها db.svc.cluster.local، وبعدها db.cluster.local... لحد ما يلاقي.
وده معناه ٤ أو ٥ استعلامات DNS بدل واحد لكل اتصال. على تطبيق بيفتح كونيكشنز كتير، دي بتبقى مشكلة أداء حقيقية وبتحمّل على الـ CoreDNS.
الحل: اكتب الاسم الكامل (بنقطة في الآخر كمان: db.production.svc.cluster.local.) في إعدادات التطبيقات اللي بتفتح كونيكشنز كتير، أو ظبّط ndots في الـ Pod spec.
Commands#
kubectl get svc # all Services with their ClusterIPs
kubectl get endpoints web # THE FIRST DEBUGGING COMMAND
kubectl describe svc web
# Test from inside the cluster — a Service is not reachable from your laptop
kubectl run tmp --rm -it --image=busybox --restart=Never -- sh
# inside: wget -qO- http://web
# inside: nslookup web
# Or reach it from your machine temporarily
kubectl port-forward svc/web 8080:80 # then open http://localhost:8080kubectl port-forward is how you test an internal Service from your laptop without exposing it to the internet.
What goes wrong#
ENDPOINTS: <none> — the most common Kubernetes bug by a wide marginThe Service exists, DNS resolves, connections are accepted — and everything times out. Because the Service has no backends.
kubectl get endpoints web
# web <none> 5m ← thisTwo possible causes:
1. The selector does not match the pods' labels. Compare them character by character:
kubectl get svc web -o jsonpath='{.spec.selector}{"\n"}'
kubectl get pods --show-labels2. The pods are not Ready. Only pods passing their readiness probe are listed. kubectl get pods showing 0/1 READY means the probe is failing — the Service is behaving correctly by refusing to send traffic there.
There is no error message for either. Always check endpoints first.
<none> دي أشهر مشكلة في كوبرنيتيس كلهالـ Service موجودة، والـ DNS بيترجم، والكونيكشن بيتقبل... وكل حاجة بتقعد تستنى لحد ما الوقت يخلص. عشان مفيش أي بود ورا الـ Service.
kubectl get endpoints web
# web <none> 5m ← دي هيوفيه سببين بس ممكن يعملوا كده. اتأكد منهم بالترتيب:
١. الـ selector مش مطابق للـ labels. قارنهم حرف بحرف:
kubectl get svc web -o jsonpath='{.spec.selector}{"\n"}'
kubectl get pods --show-labelsخد بالك من حروف كبيرة وصغيرة، ومن app مقابل App، ومن مسافة زايدة.
٢. البودات مش Ready. الـ endpoints بتحتوي على البودات الناجحة في الـ readiness probe بس.
فلو kubectl get pods بيوريك 0/1 READY، يبقى الـ probe بتفشل — و الـ Service بتتصرف صح تماماً لما بترفض تبعتلها ترافيك. المشكلة مش في الـ Service، المشكلة في التطبيق أو في الـ probe.
والحاجة القاسية: مفيش أي رسالة إيرور لا في الحالة الأولى ولا التانية. فاشيك على الـ endpoints قبل أي حاجة، دايماً.
| Symptom | Cause | Fix |
|---|---|---|
ENDPOINTS: <none> | Selector mismatch, or pods not Ready | See above |
| Connection refused | targetPort wrong | Compare to the container's real port |
ping <ClusterIP> fails | A ClusterIP is not pingable — it is a routing rule, not a host | Use wget/curl, not ping |
| Works from one pod, not another | NetworkPolicy blocking it | kubectl get networkpolicy -A |
LoadBalancer stuck on <pending> | No cloud integration (e.g. plain minikube) | Use port-forward or NodePort locally |
| Scaled up, load still uneven | Keep-alive connections pinned to old pods | See below |
Kubernetes balances connections, not requests. With HTTP keep-alive or gRPC, one long-lived connection stays pinned to one pod for its whole life.
So scaling from 3 to 30 pods moves no existing traffic. The 27 new pods are Ready, in the endpoint list, and idle.
The tell: per-pod request rates are wildly uneven while the Service looks perfectly healthy. Fixes are a maximum connection lifetime on the client, or an L7 proxy / service mesh that balances per request.
دي مشكلة بتحصل في البرودكشن كتير، والناس تقعد ساعات مش فاهمة.
كوبرنيتيس بيوزّع الـ connections، مش الـ requests.
والفرق ده بيبقى كارثة مع HTTP keep-alive و gRPC، عشان دول بيفتحوا كونيكشن واحد ويقعدوا يستخدموه لآلاف الريكوستات.
يعني إنت زوّدت ٢٧ بود، وكلهم Ready، وكلهم في لستة الـ endpoints، ومفيش ولا ريكوست واحد رايحلهم. عشان الترافيك الموجود ماشي على كونيكشنز اتفتحت خلاص واترابطت ببودات قديمة.
إزاي تعرف إن دي هي المشكلة؟ بصّ على معدّل الريكوستات لكل بود لوحده. هتلاقيه متفاوت بشكل جنوني، والـ Service شكلها سليم تماماً.
والحلول:
- حدّد عمر أقصى للكونيكشن عند العميل، عشان يقطع ويفتح من جديد ويعاد توزيعه. (أسهل حل وأسرع.)
- حط L7 proxy أو service mesh (Envoy، Linkerd) — دي بتوزّع لكل ريكوست مش لكل كونيكشن.
- لو gRPC، استخدم client-side load balancing مع headless Service.
Getting traffic in from outside#
A ClusterIP Service is internal. For HTTP from the internet you normally want an Ingress in front of it — one load balancer for many services, with hostname and path routing and TLS.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: site
spec:
ingressClassName: nginx # which controller handles this
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port: { number: 80 }An Ingress object does nothing on its own — a controller (ingress-nginx, Traefik) must be installed in the cluster to act on it. An Ingress that appears to be ignored usually means no controller, or the wrong ingressClassName.
الـ ClusterIP جوّاني، يعني الإنترنت مش شايفه. فعشان تجيب ترافيك من بره على HTTP، إنت محتاج Ingress.
وليه Ingress مش LoadBalancer لكل سيرفيس؟ عشان الفلوس والتحكّم:
| لكل سيرفيس LoadBalancer | Ingress واحد |
|---|---|
| ١٠ سيرفيسات = ١٠ لود بالانسر = ١٠ فواتير | لود بالانسر واحد |
| ١٠ شهادات TLS تظبّطها بإيدك | الشهادات في مكان واحد |
| مفيش توجيه بالمسار | /api لسيرفيس و / لسيرفيس تاني |
دي أشهر لخبطة مع الـ Ingress. إنت بتعمل kubectl apply للـ Ingress، والأمر بينجح، ومفيش أي حاجة بتحصل. وكأنه بيتجاهلك.
السبب إن الـ Ingress مجرد ورقة مكتوب فيها قواعد. لازم يكون فيه controller منصّب في الكلاستر (ingress-nginx أو Traefik) هو اللي يقرأ الورقة دي وينفّذها.
اتأكد من حاجتين:
# ١. فيه controller أصلاً؟
kubectl get pods -A | grep -i ingress
# ٢. الـ ingressClassName صح؟
kubectl get ingressclassلو الـ ingressClassName مكتوب nginx والمنصّب اسمه traefik، الـ Ingress هيتجاهل تماماً ومن غير أي شكوى.
Interview corner · الأسئلة اللي بتتسأل#
How does a Service actually route traffic? Is it a proxy? · الـ Service بروكسي؟
No, and this is the answer that shows you understand the data path.
A ClusterIP is a virtual IP that no process listens on. There is no Service pod, no Service container, nothing to SSH into. What exists is a set of packet-rewriting rules in every node's kernel.
Three things this explains immediately:
- You cannot
pinga ClusterIP. There is no host there to reply. A failing ping proves nothing at all. - Load balancing is per connection, because the pod is chosen once at DNAT time and conntrack pins it.
- kube-proxy is not in the request path. It only writes the rules. Kill kube-proxy and existing traffic keeps flowing; only new Services stop being programmed.
لأ، ومش بروكسي خالص — والإجابة دي هي اللي تبيّن إنك فاهم مسار الداتا.
الـ ClusterIP عنوان وهمي مفيش أي برنامج سامع عليه. مفيش بود اسمه Service، ومفيش كونتينر تدخله. اللي موجود فعلاً هو قواعد بتعدّل البكتات مكتوبة في كيرنل كل نود.
بص على الرسمة: البود بيبعت بكت على 10.96.0.10، وكيرنل النود بيعدّل عنوان الوصول (DNAT) لـ IP بود حقيقي، والـ conntrack بيسجّل الاختيار ده عشان باقي بكتات نفس الكونيكشن تروح لنفس البود.
وتلات حاجات بتتفسّر فوراً كده:
- مش تقدر تعمل
pingلـ ClusterIP. مفيش حاجة هناك ترد عليك. فلو الـ ping فشل، ده مش دليل على أي حاجة. استخدمcurlأوwget. - التوزيع لكل كونيكشن، عشان البود بيتختار مرة واحدة وقت الـ DNAT والـ conntrack بيثبّته.
- الـ kube-proxy مش في طريق الريكوست. هو بس بيكتب القواعد. اقتله، والترافيك الموجود يفضل ماشي — اللي بيقف بس هو إن السيرفيسات الجديدة مبقتش بتتكتب.
A rolling deploy causes a burst of 502s every time, even though the new pods are healthy. Why?
Almost always the de-registration race: the pod stops accepting traffic after it has already been sent some.
When a pod is deleted, two things happen in parallel, not in order:
The fix is a preStop hook that just sleeps:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]That looks absurd and is correct. It delays SIGTERM long enough for the endpoint removal to propagate everywhere, so the pod keeps serving during the window when nodes might still route to it. Combine with an application that handles SIGTERM by draining rather than exiting immediately.
The insight being tested is that Kubernetes is eventually consistent. Nothing is transactional; "removed from the Service" and "told to shut down" are two independent reconciliation loops with no ordering guarantee between them. Most real Kubernetes bugs are races like this one.
السبب في ٩٥٪ من الحالات هو سباق إلغاء التسجيل: البود بيبطّل يستقبل ترافيك بعد ما يكون استقبل بعضه خلاص.
لما بود يتمسح، حاجتين بيحصلوا بالتوازي، مش بالترتيب:
- الـ kubelet بيبعت
SIGTERMللكونتينر — بيبدأ يموت حالاً. - الـ endpoints controller بيشيله من الـ EndpointSlice، وبعدها كل نود لازم تعيد كتابة قواعدها — وده بياخد من ١٠٠ مللي لـ ثانيتين.
وفي الفتحة دي، البود بيقفل والنودات لسه بتبعتله. والريكوستات دي بتبقى ٥٠٢.
والحل هو preStop hook بينوّم بس:
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]شكلها سخيف، وهي صح. هي بتأخّر الـ SIGTERM مدة كفاية لحد ما خبر الشيل يوصل لكل النودات، فالبود يفضل بيخدم في الفترة اللي ممكن حد لسه يبعتله فيها.
وطبعاً مع تطبيق بيتعامل مع SIGTERM بإنه يصرّف اللي عنده، مش يقفل فوراً.
والفكرة اللي بيتقاس عليها: إن كوبرنيتيس eventually consistent. مفيش أي حاجة transactional. «اتشال من الـ Service» و «اتقاله اقفل» دول حلقتين تحكّم مستقلين تماماً، ومفيش أي ضمان لترتيبهم.
وأغلب مشاكل كوبرنيتيس الحقيقية هي سباقات زي دي بالظبط.
When would you use a headless Service, and what does it change?
Set clusterIP: None and you get a Service with no virtual IP and no routing rules. DNS then returns the pod IPs directly — one A record per Ready pod — instead of a single ClusterIP.
| Normal ClusterIP | Headless (clusterIP: None) | |
|---|---|---|
| DNS returns | one virtual IP | every Ready pod's IP |
| Load balancing | kernel, per connection | the client decides |
| Individual pods addressable | no | yes, via StatefulSet DNS names |
Use it when the client needs to know about individual pods:
- StatefulSets — a database replica set where you must reach
db-0specifically, because it is the primary. - gRPC client-side load balancing — the client gets all the addresses and balances per RPC, which fixes the keep-alive pinning problem properly.
- Peer discovery — clustered software (Cassandra, Kafka, Elasticsearch) where members find each other.
لما تكتب clusterIP: None، بتاخد Service من غير عنوان وهمي ومن غير أي قواعد توجيه. والـ DNS بيرجّعلك IPs البودات نفسها — ريكورد لكل بود Ready — بدل عنوان واحد.
| ClusterIP عادي | Headless | |
|---|---|---|
| الـ DNS بيرجّع | عنوان وهمي واحد | كل IPs البودات الجاهزة |
| التوزيع | الكيرنل، لكل كونيكشن | العميل هو اللي بيقرر |
| توصل لبود معيّن | لأ | أيوه، بأسماء الـ StatefulSet |
تستخدمها لما العميل يحتاج يعرف البودات فرادى:
- StatefulSets — داتابيز فيها replicas، ولازم توصل لـ
db-0بالتحديد عشان هو الـ primary. - gRPC — العميل بياخد كل العناوين ويوزّع لكل RPC، وده بيحل مشكلة الـ keep-alive اللي فوق حل صح مش ترقيع.
- اكتشاف الأعضاء — برامج زي كافكا وكاسندرا، اللي محتاجة الأعضاء يلاقوا بعضهم.
Key takeaways#
- Pod IPs change; a Service is the stable address.
kubectl get endpoints <svc>first, always.<none>means selector mismatch or pods not Ready.portis the Service's,targetPortis the container's.- A ClusterIP is not pingable — that failing proves nothing.
- Services are reachable from inside the cluster; use
port-forwardfrom your laptop. - Load balancing is per connection. Keep-alive defeats it.
- Ingress for HTTP from outside — and it needs a controller installed.
- الـ IPs بتتغير، والـ Service هي العنوان الثابت.
kubectl get endpoints <svc>الأول، دايماً.<none>معناها إما الـ selector غلط، وإما البودات مشReady.portبتاعة الـ Service، وtargetPortبتاعة الكونتينر.- الـ ClusterIP مش بيرد على
ping— ده عنوان وهمي مفيش حاجة سامعة عليه. فالـ ping اللي بيفشل مش دليل على حاجة. - الـ Services بتوصلها من جوه الكلاستر. من اللاب بتاعك استخدم
kubectl port-forward. - التوزيع لكل كونيكشن مش لكل ريكوست. الـ keep-alive بيبطّل التوزيع خالص، وعمل scale مش بينقل ترافيك موجود.
- Ingress للـ HTTP من بره — ولازم يكون فيه controller منصّب، وإلا الورقة بتتجاهل من غير شكوى.
Last updated 2026-09-08