> For the complete documentation index, see [llms.txt](https://bagus-cahyono.gitbook.io/programming-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bagus-cahyono.gitbook.io/programming-notes/cka/05_service_expose.md).

# Service: kubectl expose

Another way to create a service is by using `kubectl expose` command.

```bash
kubectl expose <resource-type> <resource-name> --port=<port> --target-port=<target-port> --type=<type>
```

* `<resource-type>`: Type of Kubernetes resource (**pod**, **deployment**, **replicaset**, etc.).
* `<resource-name>`: Name of the resource to expose.
* `--port`: The external port to expose.
* `--target-port`: The port on the container to forward traffic to.
* `--type`: Type of service (ClusterIP, NodePort, LoadBalancer, or ExternalName).
* `--name`: Name of newly created service.

## Expose as ClusterIP Service

First lets create a simple deployment object using `nginx` image with name `my-server`.

```bash
➜ kubectl create deployment my-server --image=nginx
deployment.apps/my-server created
```

```bash
➜ kubectl get deployments.apps                                      
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
my-server   1/1     1            0           10s
postgres    1/1     1            1           25h
simple-go   3/3     3            3           25h
```

Then lets create a service with type `ClusterIP` using `kubectl exec` command. Since `ClusterIP` is the default type we don't need to specify the `--type` argument.

```bash
➜ kubectl expose deployment my-server --port=80 --target-port=80
service/my-server exposed
```

```bash
➜ kubectl get svc my-server
NAME        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
my-server   ClusterIP   10.97.240.5   <none>        80/TCP    1m2s
```

As we can see above the command will create a service name `my-server` that exposes deployment named `my-server`. We also can get the details in `yaml` format using command `kubectl get service my-server -o yaml`.

```yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2025-02-10T06:03:41Z"
  labels:
    app: my-server
  name: my-server
  namespace: default
  resourceVersion: "129776"
  uid: 618624cb-2f7e-466e-992d-ecfaf400fb72
spec:
  clusterIP: 10.97.240.5
  clusterIPs:
  - 10.97.240.5
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: my-server
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
```

## Expose as NodePort Service

Lets delete our previous service and then expose our deployment again as `NodePort` service. We need to delete the previous service because the expose command by default will name the service same as deployment name.

```bash
➜ kubectl delete svc my-server 
service "my-server" deleted
```

Because `NodePort` is not the default service type, we need to explicitly specify the service type using `--type` in expose command.

```bash
➜ kubectl expose deployment my-server --type=NodePort --port=80 --target-port=80
service/my-server exposed
```

```bash
➜ kubectl get svc my-server 
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-server   NodePort   10.100.253.32   <none>        80:31775/TCP   8s
```

Now it exposed as `NodePort` and you can try to access it using the minikube IP and the service PORT. But if you use minikube on Mac using docker driver like me you will need `minikube service` to expose it.

## Expose as LoadBalancer Service

No lets create another service but with type `LoadBalancer`. This time we explicitly specify the service name using `--name` argument.

This command below will create a service with type `LoadBalancer` and name `my-server-lb`.

```bash
➜ kubectl expose deployment my-server --type=LoadBalancer --name=my-server-lb --port=80 --target-port=80
service/my-server-lb exposed
```

```bash
➜ kubectl get svc my-server-lb 
NAME           TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
my-server-lb   LoadBalancer   10.96.60.45   <pending>     80:30309/TCP   59s
```

Run `minikube tunnel` on the other terminal and test it using curl.

```html
➜ curl http://localhost       

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

## Expose Pod Directly

We can also directly expose a pod but this is not recommended in production.

For example lets expose the `my-server` pod.

```bash
➜ kubectl get pods 
NAME                         READY   STATUS    RESTARTS   AGE
my-server-78b5bb4ccc-7cbxf   1/1     Running   0          31m
postgres-f8f589d5b-59rw8     1/1     Running   0          25h
simple-go-5dc4557ffc-c24fp   1/1     Running   0          25h
simple-go-5dc4557ffc-d6tz6   1/1     Running   0          25h
simple-go-5dc4557ffc-gv4fv   1/1     Running   0          25h
```

```bash
➜ kubectl expose pod my-server-78b5bb4ccc-7cbxf --port=80 --target-port=80
service/my-server-78b5bb4ccc-7cbxf exposed
```

The command above will create a `ClusterIP` service that expose the pod `my-server-78b5bb4ccc-7cbxf`.

This is not recommended because as we can see below this service is tied to a specific pod. So if that pod is replaced or upgraded, the service will not work.

```bash
➜ kubectl get svc my-server-78b5bb4ccc-7cbxf -o yaml
```

```yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2025-02-10T06:34:09Z"
  labels:
    app: my-server
    pod-template-hash: 78b5bb4ccc
  name: my-server-78b5bb4ccc-7cbxf
  namespace: default
  resourceVersion: "131264"
  uid: 98ef311b-0054-4023-8d83-ca5b0bc9c025
spec:
  clusterIP: 10.99.77.89
  clusterIPs:
  - 10.99.77.89
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: my-server
    pod-template-hash: 78b5bb4ccc
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
```

## References

* <https://kubernetes.io/docs/reference/kubectl/generated/kubectl_expose/>
