Setup Ingress with vcluster

Ingress overview

An Ingress is a Kubernetes API object that defines rules for routing external HTTP/HTTPS traffic to Services running inside a vCluster. An Ingress controller enforces these rules by processing incoming requests and directing them to the appropriate backend Service.

This page demonstrates how to configure a simple Ingress that routes requests to the kuard Service based on the HTTP request path.


Before you begin

To comply with Samsung’s security policy, this Ingress resource supports only a single TLS port and assumes TLS termination at the Ingress layer. For more details, refer to the TLS section.

Because of these constraints, the following prerequisites apply when exposing your Service through an Ingress:

  • You must provide your own domain (a valid DNS subdomain) from an approved DNS provider.
  • Service access is limited to HTTPS on port 443 only (port 80 is not permitted).
  • Ingress resources are not publicly accessible—they can only be accessed from authorized IP pools such as Samsung HQ (Suwon), Joyent (SRA), SRPOL, SRIN, and SPC VPN infrastructure.
  • HTTPS and HTTP/2 protocols are supported.
  • Only TLS 1.2 and TLS 1.3 are supported (TLSv1 and TLSv1.1 are not allowed).
  • Source IP preservation is supported without requiring application modifications.
  • In EKS-based vCluster environments, proxy-protocol v2 is supported.
  • In MKS-based vCluster environments, proxy-protocol v1 is supported.

Prerequisites

  • vCluster running Kubernetes v1.25.6 or v1.26.6
  • cert-manager v1.13.1
  • Ingress-NGINX-Controller v1.9.4 (installed by operator – you do not need to install it)

Ingress resource

A minimal Ingress resource example:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: foo.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

An Ingress needs apiVersion, kind, metadata and spec fields. Each HTTP rule contains the following information:

Ingress configuration fields

  • metadata.annotations – Add Kubernetes annotations to an Ingress to customize its behavior. .

  • spec.ingressClassName – Set this to nginx to use the NGINX Ingress Controller. You may omit this field because nginx is the default.

  • spec.rules.host – When specified, the rule applies to all incoming traffic for that host.

  • spec.rules.http.paths – Define one or more path rules. Each path must specify:

    • a Service name
    • a Service port (name or number) Incoming requests are routed to a Service only if both the host and path match.
  • spec.rules.http.paths.backend – Defines the Service and port to forward matching requests to. In this environment, only HTTPS traffic is routed to the backend.


TLS

To secure an Ingress, you must reference a Kubernetes Secret that contains a TLS private key and certificate. The Ingress resource supports only one TLS port: 443, and assumes TLS termination occurs at the Ingress layer before traffic is forwarded to backend Services and Pods.

The TLS Secret must include the following keys:

  • tls.crt – the TLS certificate, base64-encoded
  • tls.key – the TLS private key, base64-encoded

Example TLS Secret

apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
  namespace: default
data:
  tls.crt: <base64-encoded certificate>
  tls.key: <base64-encoded private key>
type: kubernetes.io/tls

Referencing this Secret in an Ingress resource instructs the Ingress controller to secure client connections using TLS. Ensure that the certificate used to generate the Secret contains a valid Common Name (CN) or Fully Qualified Domain Name (FQDN) (for example, example.foo.com).

Example Ingress using TLS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  rules:
  - host: example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80
  tls:
  - hosts:
    - example.foo.com
    secretName: foo-secret

TLS with cert-manager and Let’s Encrypt

Instead of manually creating a Secret from tls.crt and tls.key, you can use cert-manager to automatically request and manage TLS certificates for your Ingress resources. This is done by adding annotations to the Ingress. cert-manager will handle creating Certificate resources and maintaining renewal on your behalf.


Deploy cert-manager

You can install cert-manager in multiple ways—for example, using Helm or through the Apps section in the KOSMOS UI. For alternative installation methods, refer to cert-manager installation documentation.

Installing from Apps

Installing App

App certificate manager

Installing using Helm

$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.13.2 \
  --set installCRDs=true

Configure a Let’s Encrypt Issuer

cert-manager provides two Kubernetes Custom Resource Definitions (CRDs) to handle certificate requests:

  • Issuers – request certificates within a namespace
  • ClusterIssuers – request certificates cluster-wide

See cert-manager docs for more details:

In this example, we will configure a ClusterIssuer that uses Let’s Encrypt production with Cloudflare DNS01 solver. For Cloudflare setup details, see Configuring DNS01 Challenge Provider .

This example solves ACME DNS01 challenges to generate Certificate resources automatically by annotating your Ingress. For HTTP01 flow, see Configuring the HTTP01 Ingress solver .

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: js.louis.you@samsung.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the DNS01 challenge provider
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token

Once edited, apply the custom resource and check on the status of the issuer after you create it:

Apply the resource and verify status using kubectl.

Name:         letsencrypt-prod
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         ClusterIssuer
Metadata:
  Creation Timestamp:  2025-10-31T02:14:15Z
  Generation:          1
  Managed Fields:
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:acme:
          .:
          f✉️
          f:privateKeySecretRef:
            .:
            f:name:
          f:server:
          f:solvers:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2023-10-31T02:14:15Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:acme:
          .:
          f:lastPrivateKeyHash:
          f:lastRegisteredEmail:
          f:uri:
        f:conditions:
          .:
          k:{"type":"Ready"}:
            .:
            f:lastTransitionTime:
            f:message:
            f:observedGeneration:
            f:reason:
            f:status:
            f:type:
    Manager:         cert-manager-clusterissuers
    Operation:       Update
    Subresource:     status
    Time:            2023-10-31T02:14:17Z
  Resource Version:  630
  UID:               25819c47-5fbc-4584-a224-d4dc77d643ce
Spec:
  Acme:
    Email:  js.louis.you@samsung.com
    Private Key Secret Ref:
      Name:  letsencrypt-prod
    Server:  https://acme-v02.api.letsencrypt.org/directory
    Solvers:
      dns01:
        Cloudflare:
          API Token Secret Ref:
            Key:   api-token
            Name:  cloudflare-api-token-secret
Status:
  Acme:
    Last Private Key Hash:  nrUJakzF1zaXMRDP5B9bUSQVkncJbRAfdlEFzzJumAk=
    Last Registered Email:  js.louis.you@samsung.com
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/1387640706
  Conditions:
    Last Transition Time:  2023-10-31T02:15:00Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

Deploy an example service

You may deploy your own chart or standalone manifests. The following example deploys a sample “kuard” application and exposes it through a Service.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kuard
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kuard
  template:
    metadata:
      labels:
        app: kuard
    spec:
      containers:
      - image: gcr.io/kuar-demo/kuard-amd64:1
        imagePullPolicy: Always
        name: kuard
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: kuard
spec:
  ports:
  - port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app: kuard

Note: Using NodePort (e.g., spec.type: NodePort) consumes NodePort quota (limits.nodeport).


Deploy a TLS Ingress resource

Once prerequisites are complete, you can request a TLS certificate in one of two ways:

1. Using annotations in the Ingress

cert-manager will detect cert-related annotations on the Ingress, issue/renew certificates, and create/update the referenced Secret.

Note: You can specify the annotations related cert-manager on your Ingress resources. Please check this document for “Supported Annotations”.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kuard
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    cert-manager.io/issuer: "letsencrypt-prod"            <------ Add annotation in here with your Issuer or Clusterissuer

spec:
  ingressClassName: nginx
  rules:
  - host: echoserver.mks.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kuard
            port:
              number: 443
  tls:
  - secretName: example-tls
    hosts:
      - echoserver.mks.example.com

cert-manager will read these annotations and use them to create a certificate, which you can request and can see:

Check certificate status:

k get certificate -A
Output

NAMESPACE   NAME               READY   SECRET             AGE
default     example-tls   True    example-tls   2m

cert-manager will reflect the state of the process for every request in the certificate object. You can view this information using this command:

k describe certificate example-tls
Output
$ k describe certificate example-tls
Name:         example-tls
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2023-11-02T00:27:18Z
  Generation:          1
  Managed Fields:
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:conditions:
    Manager:      cert-manager-certificates-trigger
    Operation:    Update
    Subresource:  status
    Time:         2023-11-02T00:27:18Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:dnsNames:
        f:isCA:
        f:issuerRef:
          .:
          f:group:
          f:kind:
          f:name:
        f:secretName:
        f:usages:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2023-11-02T00:27:18Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        f:revision:
    Manager:      cert-manager-certificates-issuing
    Operation:    Update
    Subresource:  status
    Time:         2023-11-02T00:27:24Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        f:conditions:
          k:{"type":"Ready"}:
            .:
            f:lastTransitionTime:
            f:message:
            f:observedGeneration:
            f:reason:
            f:status:
            f:type:
        f:notAfter:
        f:notBefore:
        f:renewalTime:
    Manager:         cert-manager-certificates-readiness
    Operation:       Update
    Subresource:     status
    Time:            2023-11-02T00:27:24Z
  Resource Version:  23958
  UID:               ab707f9e-1e66-4afe-bfd0-7047a418ba9b
Spec:
  Dns Names:
    bookinfo.mks.example.com
    echoserver.mks.example.com
    grpc.mks.example.com
    httpbin.mks.example.com
    kiali.mks.example.com
    nginx.mks.example.com
  Issuer Ref:
    Group:      cert-manager.io
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  example-tls
  Usages:
    digital signature
    key encipherment
    server auth
Status:
  Conditions:
    Last Transition Time:  2023-11-02T00:28:26Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2024-01-30T23:28:01Z
  Not Before:              2023-11-01T23:28:02Z
  Renewal Time:            2023-12-31T23:28:01Z
  Revision:                1
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    112s  cert-manager-certificates-trigger          Issuing certificate as Secret does not exist
  Normal  Generated  111s  cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example-tls-w8hfk"
  Normal  Requested  111s  cert-manager-certificates-request-manager  Created new CertificateRequest resource "example-tls-1"
  Normal  Issuing    105s  cert-manager-certificates-issuing          The certificate has been successfully issued

Expose services


2. Creating a Certificate resource directly

A Certificate resource defines the desired state of a certificate. cert-manager will request and manage renewal automatically.

You can read more on how to configure your Certificate resources .

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-tls
  namespace: default
spec:
  isCA: true
  secretName: example-tls
  dnsNames:
  - echoserver.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
    group: cert-manager.io
  usages:
    - digital signature
    - key encipherment
    - server auth

After apply, cert-manager will read this Certificate resource and use them to create a certificate, which you can request and see:

Verify via:

k get certificate -A
Output
NAMESPACE   NAME               READY   SECRET             AGE
default     example-tls   True    example-tls   2m39s

cert-manager will reflect the state of the process for every request in the certificate object. You can view this information using this command:

k describe certificate example-tls
Output
$ k describe certificate example-tls
Name:         example-tls
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2023-11-02T00:27:18Z
  Generation:          1
  Managed Fields:
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        .:
        f:conditions:
    Manager:      cert-manager-certificates-trigger
    Operation:    Update
    Subresource:  status
    Time:         2023-11-02T00:27:18Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:dnsNames:
        f:isCA:
        f:issuerRef:
          .:
          f:group:
          f:kind:
          f:name:
        f:secretName:
        f:usages:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2023-11-02T00:27:18Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        f:revision:
    Manager:      cert-manager-certificates-issuing
    Operation:    Update
    Subresource:  status
    Time:         2023-11-02T00:27:24Z
    API Version:  cert-manager.io/v1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        f:conditions:
          k:{"type":"Ready"}:
            .:
            f:lastTransitionTime:
            f:message:
            f:observedGeneration:
            f:reason:
            f:status:
            f:type:
        f:notAfter:
        f:notBefore:
        f:renewalTime:
    Manager:         cert-manager-certificates-readiness
    Operation:       Update
    Subresource:     status
    Time:            2023-11-02T00:27:24Z
  Resource Version:  23958
  UID:               ab707f9e-1e66-4afe-bfd0-7047a418ba9b
Spec:
  Dns Names:
    bookinfo.mks.example.com
    echoserver.mks.example.com
    grpc.mks.example.com
    httpbin.mks.example.com
    kiali.mks.example.com
    nginx.mks.example.com
  Issuer Ref:
    Group:      cert-manager.io
    Kind:       ClusterIssuer
    Name:       letsencrypt-prod
  Secret Name:  example-tls
  Usages:
    digital signature
    key encipherment
    server auth
Status:
  Conditions:
    Last Transition Time:  2023-11-02T00:28:26Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2024-01-30T23:28:01Z
  Not Before:              2023-11-01T23:28:02Z
  Renewal Time:            2023-12-31T23:28:01Z
  Revision:                1
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    112s  cert-manager-certificates-trigger          Issuing certificate as Secret does not exist
  Normal  Generated  111s  cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example-tls-w8hfk"
  Normal  Requested  111s  cert-manager-certificates-request-manager  Created new CertificateRequest resource "example-tls-1"
  Normal  Issuing    105s  cert-manager-certificates-issuing          The certificate has been successfully issued

Once ready, cert-manager will generate the TLS Secret referenced in your Ingress.

k describe secret example-tls
Output
Name:         example-tls
Namespace:    default
Labels:       controller.cert-manager.io/fao=true
Annotations:  cert-manager.io/alt-names:
                bookinfo.mks.example.com,echoserver.mks.example.com,grpc.mks.example.com,httpbin.mks.example.com,kiali.mks.example...
              cert-manager.io/certificate-name: example-tls
              cert-manager.io/common-name: bookinfo.mks.example.com
              cert-manager.io/ip-sans:
              cert-manager.io/issuer-group: cert-manager.io
              cert-manager.io/issuer-kind: ClusterIssuer
              cert-manager.io/issuer-name: letsencrypt-prod
              cert-manager.io/uri-sans:

Type:  kubernetes.io/tls

Data
====
tls.crt:  5766 bytes
tls.key:  1675 bytes

Update your Ingress to reference that Secret.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
  name: kuard
spec:
  ingressClassName: nginx
  rules:
  - host: echoserver.mks.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kuard
            port:
              number: 443
  tls:
  - secretName: example-tls
    hosts:
      - echoserver.mks.example.com

Verify ingress status and hostname assignment:

k get ingress
Output
| NAME  |  CLASS  | HOSTS  |  ADDRESS | PORTS    | AGE      |
|-------|---------|--------|----------|---------|----------|
| kuard |  nginx  | echoserver.mks.example.com   |a2bb84a10ddad46ada764c79b020ebe8-4010412680.elb.ap-southeast-1.samsungspc.cloud |  80, 443  | 4h9m |
k describe ingress kuard

Output
Name:             kuard
Namespace:        default
Address:          a2bb84a10ddad46ada764c79b020ebe8-4010412680.elb.ap-southeast-1.samsungspc.cloud
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  example-tls terminates echoserver.mks.example.com
Rules:
  Host                              Path  Backends
  ----                              ----  --------
  echoserver.mks.example.com
                                    /   kuard:443 (172.16.81.66:8080,172.16.85.220:8080,172.16.86.75:8080)
Annotations:                        nginx.ingress.kubernetes.io/ssl-redirect: false
Events:          <none>

Next, configure DNS (e.g., via your registrar) by creating a CNAME record pointing to the LoadBalancer address.


Governance

To protect workloads and enforce best practices for all vClusters, the following policies apply:

  • Only the nginx Ingress class is permitted
  • Hostnames must be explicitly defined
  • Wildcard hosts are not allowed
  • Certain path patterns are rejected to mitigate:
    • CVE-2021-25745
    • CVE-2021-25746
  • CVE-2023-5043, CVE-2023-5044, CVE-2022-4886 mitigations enabled
  • Host + path combinations must be unique across clusters
  • WAF is automatically enabled to monitor malicious traffic

Web Application Firewall (WAF)

All Ingress resources are protected by a managed WAF (Web Application Firewall). No action is required from users.

Example – WAF detecting a malicious path traversal attempt:

curl https://example.com/etc/passwd

Wallarm

Troubleshooting

“propagation check failed” during ACME DNS01 challenge

cert-manager validates DNS propagation before attempting DNS challenges. This may fail when IP addresses of DNS provider traffic is blocked.

Use cert-manager flags to override recursive DNS resolution behavior:

  • --dns-recursive-nameservers : comma separated string with host and port of the recursive nameservers cert-manager should query.

  • --dns-recursive-nameservers-only : forces cert-manager to only use the recursive nameservers for verification. Enabling this option could cause the DNS01 self check to take longer due to caching performed by recursive nameservers.

Example usage:

extraArgs:
  --dns-recursive-nameservers-only
  --dns-recursive-nameservers=kube-dns.kube-system.svc.cluster.local:53

Edit this page on GitHub