App Needs Network Access? Configure Internal and External Kubernetes Services

Your application has three running Pods, but their IP addresses can change when Kubernetes replaces them. A Service gives clients a stable address and directs connections to matching, ready Pods. Clients use the Service instead of tracking individual Pod addresses.

Reuse aks-ctlab-weu in UK West, namespace ct-apps, and the ct-web Deployment from the replicas trip. Start the cluster if stopped and confirm its three Pods are ready. Keep your Kubernetes administration access from the cluster trip.

We create two Services selecting the same Pods:

Service Type Who uses it?
ct-web-internal ClusterIP Applications inside the cluster, using a cluster-internal IP or DNS name
ct-web-public LoadBalancer Clients outside the cluster, using an Azure public IP

Use the Azure portal for all steps. A small test Pod will make the internal HTTP request and write the result to its logs. The public Service exposes only our sample NGINX page over HTTP; load-balancer networking and public IP resources can add charges.

Create the Internal Service

Open Kubernetes services → aks-ctlab-weu → Kubernetes resources → Services and ingresses → Create → Apply a YAML.

Paste the following manifest and choose Add or Apply, as shown in your portal:

apiVersion: v1
kind: Service
metadata:
  name: ct-web-internal
  namespace: ct-apps
spec:
  type: ClusterIP
  selector:
    app: ct-web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
Field What it does
namespace: ct-apps Creates the Service in the same namespace as the application
selector: app: ct-web Finds Pods with the matching label in that namespace
port: 80 Port clients connect to on the Service
targetPort: 80 Port NGINX listens on inside the selected Pods

A Service selects Pod labels, not the Deployment’s name. Both happen to use ct-web in this lab, but the selector must match the labels under the Deployment’s Pod template.

Refresh Services and ingresses, filter to ct-apps, and open ct-web-internal. Confirm Type: ClusterIP, an assigned Cluster IP, and TCP port 80. No public external IP is expected. The ClusterIP stays stable for the life of this Service, even when Pods are replaced.

AKS Service ct-web-internal showing type ClusterIP and port 80.

Test Access from Inside the Cluster

The internal address is not an internet endpoint. Opening its ClusterIP on your laptop, or making a request directly from ordinary Cloud Shell, is not the correct test. Run a client inside the cluster.

Open Create → Apply a YAML again and apply:

apiVersion: v1
kind: Pod
metadata:
  name: ct-service-check
  namespace: ct-apps
spec:
  restartPolicy: Never
  containers:
    - name: check
      image: busybox:1.37
      command:
        - sh
        - -c
        - wget -S -O - -T 15 http://ct-web-internal.ct-apps.svc.cluster.local
      resources:
        requests:
          cpu: 10m
          memory: 16Mi
        limits:
          cpu: 100m
          memory: 32Mi

This temporary Pod uses wget to request the Service’s DNS name. ct-web-internal is the Service name, ct-apps is its namespace, and svc.cluster.local is the default cluster DNS suffix. An application in the same namespace can simply use http://ct-web-internal.

Open Kubernetes resources → Workloads → Pods, filter to ct-apps, and select ct-service-check → Logs. Select the check container if prompted. Expect HTTP/1.1 200 OK and HTML containing Welcome to nginx!.

The Pod should finish with Completed because it performs one request and exits. That is success for this test, even though it is not a continuously running application Pod. If you need to repeat the test, delete only ct-service-check and apply its manifest again.

If the portal’s live logs remain empty, open Cloud Shell → Bash and run the complete block below. It reconnects kubectl to the lab cluster using your Azure CLI sign-in, then retrieves the test container’s logs, including after it has finished:

az account set --subscription "CloudTrips TEST"

az aks get-credentials \
  --resource-group rg-cloudtrips-aks-test-weu \
  --name aks-ctlab-weu \
  --overwrite-existing

kubelogin convert-kubeconfig -l azurecli

kubectl logs ct-service-check -n ct-apps -c check

Expect HTTP/1.1 200 OK and Welcome to nginx! in the output. A container state of Terminated is normal after this one-time test if its reason is Completed and its exit code is 0. A nonzero exit code indicates a failed test. If Azure CLI reports an expired login, restart Cloud Shell and repeat the block. Reconnecting first also addresses the kubectl error “You must be logged in to the server” when its connection configuration or authentication needs refreshing.

Logs of ct-service-check showing a successful internal HTTP request and the NGINX page.

Create the External Service

Return to Services and ingresses → Create → Apply a YAML and apply:

apiVersion: v1
kind: Service
metadata:
  name: ct-web-public
  namespace: ct-apps
spec:
  type: LoadBalancer
  selector:
    app: ct-web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

This creates a second route to the same three Pods; it does not create another Deployment or more replicas. AKS configures Azure load-balancer resources and a public frontend address for this Service. You do not need to create a load balancer manually or open port 80 on each node yourself.

Refresh the Services list with ct-apps selected. ct-web-public should show Type: LoadBalancer. Its external IP may initially be Pending; wait a few minutes and refresh until an address appears.

Copy the external/public IP, not the ClusterIP. Open it in your browser using this format, replacing the placeholder with the actual IP:

http://<external-ip>/

Use http://, because this lab does not configure a TLS certificate or HTTPS listener. Expect the Welcome to nginx! page. If the browser upgrades the URL to HTTPS, enter the HTTP URL explicitly.

Browser displaying the NGINX welcome page through the public Service IP.

Both addresses now reach the application: the internal DNS name from a Pod, and the public IP from your browser. Kubernetes updates the Service’s backend endpoints as matching Pods become ready or disappear. Connections can reach different replicas, but identical NGINX pages will look the same; refreshing is not proof that traffic rotated through every Pod.

If a request fails, confirm the three ct-web Pods are ready and the Service selector is exactly app: ct-web in ct-apps. Open the Service’s details and Events to inspect provisioning errors. A Service without matching ready Pods has no healthy application destination.

Clean Up the Public Endpoint

When finished testing, select ct-web-public under Services and ingresses and choose Delete. This removes the public Service and triggers cleanup of its Azure networking configuration. Do not manually delete the cluster’s shared load balancer or other resources in the MC_ node resource group.

Delete ct-service-check under Workloads → Pods. Keep ct-web-internal and the Deployment for further exercises, or delete the internal Service if no longer needed. Deleting a Service leaves the application Pods running; deleting the ct-apps namespace removes everything in it.

The cluster’s worker VMs continue to cost money. Use Overview → Stop between sessions, remembering that retained networking and disks can still incur charges, or delete the cluster’s lab resource group when finished with AKS.