Introduction
A running application needs more than healthy Pods. It also needs a reliable way to receive traffic and a secure, maintainable approach to handling configuration.
In this mission, I extended the NGINX Deployment on Azure Kubernetes Service (AKS) by creating internal and external Services, testing application access, and separating configuration values from the container image. I also created a Kubernetes Secret, verified its stored value, and injected both ConfigMap and Secret data into the running Deployment as environment variables.
The implementation demonstrates how Kubernetes networking and configuration resources work together to make workloads accessible and configurable without rebuilding their images.
What This Part Covers
- Creating a
ClusterIPService for internal application access. - Testing the NGINX application inside the cluster.
- Exposing the Deployment through a
LoadBalancerService. - Verifying the public endpoint.
- Creating a ConfigMap from literal values.
- Creating a Secret for a sensitive configuration value.
- Injecting ConfigMap and Secret values into the Deployment.
- Verifying the environment variables inside a running container.
1. Create a ClusterIP Service
I started by defining an internal Service in manifests/03-service-clusterip.yaml.
cat << 'EOF' > manifests/03-service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
The manifest defines a Service named web-svc with the following configuration:
-
type: ClusterIPcreates an internal Service address that is reachable from within the cluster. -
selector: app: webidentifies the Pods that receive traffic through the Service. -
port: 80specifies the port exposed by the Service. -
targetPort: 80specifies the destination port on the selected Pods.
The selector connects the Service to the web Deployment's Pods, which carry the app: web label.
This is an important distinction: clients communicate with the Service rather than depending on an individual Pod IP address. Kubernetes maintains the Service's routing to the eligible backend Pods as the workload changes.
I applied the manifest:
kubectl apply -f manifests/03-service-clusterip.yaml
The Service provides a stable internal endpoint for the application. It does not, by itself, expose the application directly to the public internet.
2. Test Internal Application Access
After configuring the internal Service, I tested access to the NGINX application from inside the Kubernetes environment.
The response contained the default NGINX welcome-page HTML, including:
<title>Welcome to nginx!</title>
The response also contained the page's standard message confirming that the NGINX web server was installed and running.
This verified that the application could respond to an HTTP request from within the cluster.
Testing internal connectivity before exposing a workload externally is useful because it separates application and internal networking checks from the additional configuration required for public access.
3. Expose the Application with a LoadBalancer Service
For external access, I created a second Service in manifests/04-service-loadbalancer.yaml.
cat << 'EOF' > manifests/04-service-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
name: web-public
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
EOF
The Service uses the same app: web selector as the internal Service, so it routes traffic to the Pods managed by the Deployment.
The key difference is:
type: LoadBalancer
On AKS, this Service type requests an Azure load balancer configuration that can expose the application through an external IP address.
I applied the manifest:
kubectl apply -f manifests/04-service-loadbalancer.yaml
Kubernetes confirmed that the Service was created:
service/web-public created
I then watched the Service status:
kubectl get service web-public --watch
The output showed:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
web-public LoadBalancer 10.0.73.23 20.61.154.190 80:31236/TCP
The assigned addresses and port information were:
-
Cluster IP:
10.0.73.23 -
External IP:
20.61.154.190 -
Service port:
80 -
NodePort:
31236
The external IP made the NGINX application reachable through the load balancer.
Verify the public endpoint
I tested the external endpoint using curl:
curl -s http://$(kubectl get service web-public -o jsonpath='{.status.loadBalancer.ingress[0].ip}') | grep title
The response was:
<title>Welcome to nginx!</title>
This confirmed that the request reached the NGINX application through the public Service endpoint.
A LoadBalancer Service is useful when an application needs an externally reachable endpoint. However, public exposure should be deliberate: production workloads may also require network restrictions, TLS termination, ingress routing, and appropriate access controls.
4. Create a ConfigMap
Application configuration should not always be embedded in a container image. Kubernetes ConfigMaps provide a way to store non-sensitive configuration separately from application code and image builds.
I generated a ConfigMap manifest from literal values:
kubectl create configmap web-config \
--from-literal=APP_ENV=production \
--from-literal=WELCOME_MSG="Hello from AKS" \
--dry-run=client -o yaml > manifests/05-configmap.yaml
The command generated the YAML locally rather than immediately creating the resource in the cluster. The output was saved to manifests/05-configmap.yaml.
The configuration contained two values:
| Key | Value |
|---|---|
APP_ENV |
production |
WELCOME_MSG |
Hello from AKS |
I then applied the generated manifest:
kubectl apply -f manifests/05-configmap.yaml
Kubernetes confirmed:
configmap/web-config created
To inspect the stored configuration, I ran:
kubectl get configmap web-config -o yaml
The output included:
data:
APP_ENV: production
WELCOME_MSG: Hello from AKS
This verified that the ConfigMap contained the expected configuration values.
Because the ConfigMap is separate from the container image, these values can be managed independently of the image build. ConfigMaps are intended for non-sensitive data and should not be used to store credentials or other confidential values.
5. Create a Kubernetes Secret
For the next configuration value, I created a Kubernetes Secret named web-secret.
kubectl create secret generic web-secret \
--from-literal=API_KEY=change-me-123
Kubernetes returned:
secret/web-secret created
The Secret contains the key API_KEY, with a placeholder value used in this lab.
Unlike a ConfigMap, a Secret is designed for sensitive configuration data. However, creating a Kubernetes Secret does not automatically make the value safe: access controls, encryption at rest, and appropriate secret-management practices still matter.
Verify the stored value
I verified the value using:
kubectl get secret web-secret -o jsonpath='{.data.API_KEY}' | base64 --decode; echo
The output was:
change-me-123
Kubernetes stores Secret data in a base64-encoded representation in the API object. Base64 is an encoding mechanism, not encryption. Anyone authorized to retrieve and decode the value can read it.
The value used here is only a lab placeholder. Real API keys and production credentials should not be committed to a repository or exposed in terminal output, screenshots, or documentation.
6. Inject the ConfigMap into the Deployment
With the ConfigMap and Secret created, I configured the web Deployment to consume their values as environment variables.
First, I added the ConfigMap values:
kubectl set env deployment/web --from=configmap/web-config
Kubernetes confirmed:
deployment.apps/web env updated
This added the ConfigMap's key-value pairs to the container environment defined by the Deployment.
The values supplied by web-config were:
APP_ENV=productionWELCOME_MSG=Hello from AKS
7. Inject the Secret into the Deployment
I then added the Secret value to the Deployment:
kubectl set env deployment/web --from=secret/web-secret
Kubernetes returned:
deployment.apps/web env updated
This configured the container environment to include API_KEY from web-secret.
Changing the Deployment's Pod template triggered a rollout. I verified that the rollout completed:
kubectl rollout status deployment/web
The output confirmed:
deployment "web" successfully rolled out
The new Pods were created using the updated Deployment configuration.
Verify the environment variables
To verify that the values were available inside a running container, I executed:
kubectl exec deploy/web -- printenv APP_ENV WELCOME_MSG API_KEY
The output was:
production
Hello from AKS
change-me-123
This confirmed that the container received the values from both Kubernetes configuration resources.
The ConfigMap supplied the non-sensitive application configuration, while the Secret supplied the placeholder API key. Both were injected into the container environment without rebuilding the NGINX image.
For real workloads, avoid printing secrets to logs or terminal output unnecessarily. When applications support it, mounting secrets as files or retrieving credentials from an external secret manager may be more appropriate, depending on the security requirements.
8. ClusterIP vs. LoadBalancer
The two Services created in this mission serve different networking purposes.
| Feature | ClusterIP |
LoadBalancer |
|---|---|---|
| Primary purpose | Internal application access | External application access |
| Default reachability | Within the cluster | Through an external load balancer when provisioned |
| Stable Service endpoint | Yes | Yes |
| Typical use | Internal APIs and service-to-service traffic | Applications that require a public or externally reachable endpoint |
Both Services use label selectors to route traffic to the appropriate Pods. The choice of Service type depends on where the clients are located and how the application should be exposed.
9. Operational Takeaways
This mission brought together Kubernetes networking and configuration management.
The main operational takeaways were:
- Stable internal networking: A ClusterIP Service provides an internal endpoint independent of individual Pod IP addresses.
- External access: A LoadBalancer Service can expose an application through an Azure-managed external endpoint.
- Separation of configuration: ConfigMaps allow non-sensitive settings to be managed separately from the container image.
- Secret handling: Kubernetes Secrets provide a dedicated resource for sensitive values, but they still require appropriate security controls.
- Environment injection: ConfigMap and Secret values can be supplied to containers through the Deployment's environment configuration.
-
Rollout verification: Updating the Deployment's Pod template triggers a rollout, which can be monitored with
kubectl rollout status. - Connectivity testing: Testing both internal and external access verifies different parts of the application networking path.
Conclusion
The web Deployment was made accessible through an internal ClusterIP Service and an external LoadBalancer Service. The public endpoint returned the NGINX welcome page, confirming that external access was working.
I also created web-config and web-secret, injected their values into the Deployment, and verified the resulting environment variables inside a running container.
The result is a workload with a stable networking interface and configuration managed independently of its container image. These patterns form an important foundation for applications that need controlled connectivity and maintainable configuration on AKS.
In Part 6, the series will continue with the next mission, building on the networking and configuration resources established here.
This article is Part 5 of the nine-part series, “Kubernetes on AKS: From Zero to Production Workloads.”









Top comments (1)
Reliable applications need more than running Pods, they need stable networking and a secure approach to configuration. This mission demonstrates how Kubernetes Services provide internal and external access, while ConfigMaps and Secrets separate configuration from the container image. One important takeaway is that Kubernetes Secrets still require proper access controls because base64 encoding alone is not encryption. Building these practices into an AKS environment is essential for more secure and maintainable deployments.