> ## Documentation Index
> Fetch the complete documentation index at: https://io.net/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Exposing Applications to the Internet

> Learn how to expose applications to the internet on io.net Kubernetes clusters using ingress controllers, with guidance on DNS automation and SSL certificate management.

This guide provides a comprehensive overview of how to expose applications to the internet on **io.net** *Kubernetes Clusters* using an ingress controller. It details two supported deployment approaches, outlines their respective advantages and trade-offs, and explains how DNS can be automated with *ExternalDNS*. The guide also covers SSL certificate management using cert-manager to support secure application delivery.

## Prerequisites

* `kubectl` access to your io.net Kubernetes Cluster.
* `Helm 3.x` installed.
* Domain ownership and DNS management access.
* Basic understanding of Kubernetes concepts (Pods, Services, Ingress).
* For *ExternalDNS*: API credentials for your DNS provider.

## Selecting your Method

This table outlines the key factors to consider when choosing between **Deployment + Service IPs** and **DaemonSet**.

| Key Factor            | Deployment + Service IPs        | DaemonSet                      |
| --------------------- | ------------------------------- | ------------------------------ |
| **Setup Complexity**  | Medium                          | Simple                         |
| **Scalability**       | High, supports flexible scaling | Limited, one pod per node      |
| **High Availability** | Requires manual IP management   | Built-in with multiple nodes   |
| **Resource Usage**    | Configurable                    | Fixed per node                 |
| **Best For**          | Large-scale applications        | Simple deployments, edge cases |

<Tabs>
  <Tab title="Option 1">
    ## Option 1: Ingress Controller as a Deployment with Service IPs

    The ingress controller is deployed as a scalable Kubernetes deployment and exposed via a LoadBalancer Service with manually assigned external IP addresses.

    Incoming traffic is automatically load-balanced across ingress pods. This approach is fully compatible with ExternalDNS.

    ### Flow overview:

    ```
    [ Client ]
       |
       v
    Connects to node public IPs (set as externalIPs on LoadBalancer service)
       |
       v
    Traffic → balanced across ingress controller pods
       |
       v
    Ingress controller routes to applications
    ```

    ### How it works:

    * ***Service Type***: `LoadBalancer` (required for ExternalDNS compatibility).
    * ***External IPs***: Manually assigned and mapped to the nodes where ingress pods are scheduled.
    * ***DNS Management***: ExternalDNS monitors the LoadBalancer Service and automatically creates DNS records that point to the configured external IPs.

    | Pros                                                 | Cons                                            |
    | :--------------------------------------------------- | :---------------------------------------------- |
    | Automatic load balancing across ingress controllers. | Node failures impact the assigned public IPs.   |
    | No host port conflicts.                              | Public IPs must be managed manually.            |
    | Ingress controllers can scale flexibly.              | No automatic failover without additional tools. |
    | Cleaner setup for most applications.                 |                                                 |

    ### Step-by-Step Setup:

    <Steps>
      <Step title="Install NGINX Ingress Controller as a Deployment">
        <Warning>
          The *Pod Security Admission* plugin is enabled by default in all [io.net](http://io.net) Kubernetes clusters to enforce *Pod Security Standards* and enhance baseline cluster security.

          You may override *Pod Security Admission* settings at the namespace level when necessary. This can be useful for workloads such as ingress controllers or monitoring solutions that require less restrictive security policies.

          However, the recommended approach is to adapt your applications to run securely by configuring an appropriate `podSecurityContext`.
        </Warning>

        ```bash theme={null}
        kubectl create namespace ingress-nginx
        kubectl label namespace ingress-nginx pod-security.kubernetes.io/enforce=privileged

        helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
        helm repo update
        helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
          --namespace ingress-nginx \
          --set controller.kind=Deployment \
          --set controller.replicaCount=3 \
          --set controller.resources.requests.cpu=100m \
          --set controller.resources.requests.memory=128Mi \
          --set controller.service.enabled=true \
          --set controller.service.type=LoadBalancer \
          --set controller.publishService.enabled=false \
          --set-string controller.nodeSelector.worker-node=true
        ```

        <Note>
          The ingress controller is configured to deploy only on worker nodes with the `worker-node=true` label.
        </Note>

        <Info>
          **Optional:** add *Tolerations* if your worker nodes have taints:

          `--set 'controller.tolerations[0].operator=Exists'`
        </Info>

        <Info>
          **Optional:** add *Pod Anti-Affinity* to ensure pod replicas are scheduled on different nodes (only if you have sufficient worker nodes):

          `--set 'controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchLabels.app\.kubernetes\.io/name=ingress-nginx'`

          `--set 'controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey=kubernetes.io/hostname'`
        </Info>
      </Step>

      <Step title="Assign Public IPs to Ingress Nodes">
        Edit the Service configuration so that Public IPs are assigned only from nodes where ingress pods are running.

        ```bash theme={null}
        kubectl patch svc ingress-nginx-controller -n ingress-nginx --type=merge -p "{
          \"spec\": {
            \"externalIPs\": [
              $(kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -o jsonpath='{.items[*].spec.nodeName}' \
                | tr ' ' '\n' | sort -u | while read node; do \
                  kubectl get node "$node" -o jsonpath='{.status.addresses[?(@.type=="ExternalIP")].address}'; \
                  echo; \
                done | tr '\n' ',' | sed 's/,$//' | sed 's/\([^,]*\)/"\1"/g')
            ]
          }
        }"
        ```

        <Note>
          This command identifies the nodes currently running ingress controller pods and assigns their external IP addresses to the LoadBalancer Service. This allows ExternalDNS to correctly detect the service endpoints and manage the corresponding DNS records.
        </Note>
      </Step>

      <Step title="Update DNS Records">
        Point your domain to the Service’s `EXTERNAL-IP` values shown by `kubectl get svc -n ingress-nginx`, or follow the [*ExternalDNS Guide*](/guides/clouds/kubernetes/externaldns-guide).
      </Step>

      <Step title="Deploy Applications with Ingress">
        Deploy your applications and configure ingress resources to handle routing to the appropriate services.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Option 2">
    ## Option 2: Ingress Controller as a DaemonSet

    Run the ingress controller on every cluster node, where it listens directly on ports 80 and 443 of each node’s public IP. Your domain can be configured to point to multiple node IPs for traffic ingress.

    ### Flow overview:

    ```
    [ Client ]
       |
       v
    DNS round robin → node public IPs
       |
       +--> Node A: ingress controller (listens 80/443) → routes to applications
       +--> Node B: ingress controller (listens 80/443) → routes to applications
       +--> Node C: ingress controller (listens 80/443) → routes to applications
    ```

    ### How it works:

    * ***Workload Type:*** DaemonSet (one ingress controller pod per node).
    * ***Traffic Exposure:*** The ingress controller listens directly on ports **80/443** of each node’s public IP.
    * ***DNS Management:*** DNS records are configured to point directly to the public IPs of all nodes running the ingress controller (for example, using DNS round-robin).

    | Pros                                                                                 | Cons                                                                             |
    | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- |
    | Simple and straightforward to set up.                                                | Relies on DNS round robin when no external load balancer is used.                |
    | Every node can accept traffic directly.                                              | DNS records may continue to point to unavailable nodes until they are refreshed. |
    | High availability through multiple node public IPs.                                  | Ports 80/443 are reserved on every node.                                         |
    | Can combine with an external load balancer for a more advanced traffic distribution. | Scaling is limited to one ingress pod per node.                                  |

    ### Step-by-Step Setup:

    <Steps>
      <Step title="Install NGINX Ingress Controller as a DaemonSet">
        <Warning>
          The *Pod Security Admission* plugin is enabled by default in all [io.net](http://io.net) Kubernetes clusters to enforce *Pod Security Standards* and enhance baseline cluster security.

          You may override *Pod Security Admission* settings at the namespace level when necessary. This can be useful for workloads such as ingress controllers or monitoring solutions that require less restrictive security policies.

          However, the recommended approach is to adapt your applications to run securely by configuring an appropriate `podSecurityContext`.
        </Warning>

        ```bash theme={null}
        kubectl create namespace ingress-nginx
        kubectl label namespace ingress-nginx pod-security.kubernetes.io/enforce=privileged

        helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
        helm repo update
        helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
          --namespace ingress-nginx \
          --set controller.kind=DaemonSet \
          --set controller.hostNetwork=true \
          --set controller.publishService.enabled=false \
          --set controller.dnsPolicy=ClusterFirstWithHostNet \
          --set controller.resources.requests.cpu=100m \
          --set controller.resources.requests.memory=128Mi \
          --set controller.service.enabled=true \
          --set controller.service.type=ClusterIP \
          --set controller.service.clusterIP=None \
          --set controller.admissionWebhooks.enabled=false \
          --set-string controller.nodeSelector.worker-node=true
        ```

        <Warning>
          Using `hostNetwork=true` reserves ports 80/443 on every node. Use `nodeSelector` and `tolerations` if you only want ingress on specific nodes.

          `--set 'controller.tolerations[0].operator=Exists'`

          `--set 'controller.nodeSelector.node-role\.kubernetes\.io/hostname='`
        </Warning>
      </Step>

      <Step title="Configure DNS for Node IPs">
        Point your domain to the public IPs of all nodes running the ingress controller, or follow the [*ExternalDNS Guide*](/guides/clouds/kubernetes/externaldns-guide).
      </Step>

      <Step title="Deploy Applications with Ingress">
        Deploy your applications and define Ingress resources to route incoming traffic to the appropriate services.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## SSL/TLS Certificate Management

When exposing applications to the internet through a Kubernetes ingress controller, SSL/TLS certificates are required to securely terminate HTTPS traffic. `cert-manager` automates the issuance and renewal of certificates from **Let’s Encrypt**, integrating directly with Kubernetes Ingress resources to provide end-to-end HTTPS without manual certificate management.

In the setup below, `cert-manager` is installed in the cluster and configured to use a **DNS-01 challenge**, which is well suited for internet-facing applications, wildcard domains, and environments where ingress traffic reaches services through public IPs.

The following examples show how to install `cert-manager` and configure it with **Cloudflare** as the DNS provider.

Read more: [https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/](https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/)

### `cert-manager` with *Let's Encrypt* (Recommended)

```bash theme={null}
kubectl create namespace cert-manager
kubectl label namespace cert-manager pod-security.kubernetes.io/enforce=privileged

# Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --set installCRDs=true

### Example for Cloudflare - https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-key-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-key: ${CLOUDFLARE_API_KEY}
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: ${cloudflare_email}
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-dns01-private-key
    solvers:
    - dns01:
        cloudflare:
          email: ${cloudflare_email}
          apiKeySecretRef:
            name: cloudflare-api-key-secret
            key: api-key
EOF
```

### Example for Cloudflare with API Token (Recommended)

<Expandable title="Example">
  ```bash theme={null}
  cat <<EOF | kubectl apply -f -
  apiVersion: v1
  kind: Secret
  metadata:
    name: cloudflare-api-token-secret
    namespace: cert-manager
  type: Opaque
  stringData:
    api-token: ${CLOUDFLARE_API_TOKEN}
  ---
  apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: letsencrypt-prod
  spec:
    acme:
      email: ${YOUR_EMAIL}
      server: https://acme-v02.api.letsencrypt.org/directory
      privateKeySecretRef:
        name: letsencrypt-dns01-private-key
      solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token
  EOF
  ```
</Expandable>

### Example for Cloudflare with API Key

<Expandable title="Example">
  ```bash theme={null}
  cat <<EOF | kubectl apply -f -
  apiVersion: v1
  kind: Secret
  metadata:
    name: cloudflare-api-key-secret
    namespace: cert-manager
  type: Opaque
  stringData:
    api-key: ${CLOUDFLARE_API_KEY}
  ---
  apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: letsencrypt-prod
  spec:
    acme:
      email: ${CLOUDFLARE_EMAIL}
      server: https://acme-v02.api.letsencrypt.org/directory
      privateKeySecretRef:
        name: letsencrypt-dns01-private-key
      solvers:
      - dns01:
          cloudflare:
            email: ${CLOUDFLARE_EMAIL}
            apiKeySecretRef:
              name: cloudflare-api-key-secret
              key: api-key
  EOF
  ```

  ##
</Expandable>

<Tip>
  For an end-to-end HTTPS application example, refer to [Quick Start: Hello World with HTTPS](/guides/clouds/kubernetes/quick-start).
</Tip>

## Troubleshooting

### Health Checks

<AccordionGroup>
  <Accordion title="Check Ingress Controller Status" icon="circle-check">
    ```bash theme={null}
    kubectl get pods -n ingress-nginx
    kubectl logs -f deployment/nginx-ingress-ingress-nginx-controller -n ingress-nginx
    ```
  </Accordion>

  <Accordion title="Check Ingress Resources" icon="circle-check">
    ```bash theme={null}
    kubectl get ingress --all-namespaces
    kubectl describe ingress hello-ingress -n hello


    ```
  </Accordion>
</AccordionGroup>

### Common Issues and Solutions

<AccordionGroup>
  <Accordion title="DNS Not Resolving" icon="circle-exclamation">
    ```bash theme={null}
    # Check DNS propagation
    nslookup app.example.com
    dig app.example.com

    # Check ExternalDNS logs
    kubectl logs -f deployment/external-dns -n external-dns
    ```
  </Accordion>

  <Accordion title="SSL Certificate Issues" icon="circle-exclamation">
    ```bash theme={null}
    # Check certificate status
    kubectl get certificates --all-namespaces
    kubectl describe certificate hello-tls -n hello

    # Check cert-manager logs
    kubectl logs -f deployment/cert-manager -n cert-manager
    ```
  </Accordion>

  <Accordion title="Application Not Accessible" icon="circle-exclamation">
    ```bash theme={null}
    # Check ingress configuration
    kubectl describe ingress hello-ingress -n hello

    # Test backend service directly
    kubectl port-forward svc/hello-world 8080:80 -n hello
    # Then test: curl localhost:8080

    # Check ingress controller logs
    kubectl logs -f deployment/nginx-ingress-ingress-nginx-controller -n ingress-nginx
    ```
  </Accordion>

  <Accordion title="High Resource Usage" icon="circle-exclamation">
    ```bash theme={null}
    # Check resource usage
    kubectl top pods -n ingress-nginx
    kubectl describe pod <ingress-controller-pod> -n ingress-nginx

    # Adjust resource limits
    helm upgrade nginx-ingress ingress-nginx/ingress-nginx \
      --namespace ingress-nginx \
      --set controller.resources.limits.cpu=2000m \
      --set controller.resources.limits.memory=1Gi
    ```
  </Accordion>

  <Accordion title="Debug Commands" icon="circle-exclamation">
    ```bash theme={null}
    # Get all ingress-related resources
    kubectl get all,ingress,certificates -n ingress-nginx
    kubectl get all,ingress,certificates -n your-app-namespace

    # Test connectivity
    kubectl run test-pod --image=busybox --rm -it -- sh
    # Inside pod: wget -O- http://your-service.namespace.svc.cluster.local
    ```
  </Accordion>
</AccordionGroup>
