Tutorial: Deploy Access Server on GKE with the Helm Chart⁠

Overview⁠

This tutorial guides you through deploying OpenVPN Access Server on a Google Kubernetes Engine (GKE) Standard cluster using the Access Server Helm chart.

The deployment uses Ubuntu nodes, persistent storage, a regional static IP, and a single mixed-protocol external load balancer that exposes:

  • TCP 943 for the Admin Web UI and Client Web UI.

  • TCP 443 for web services and OpenVPN TCP connections.

  • UDP 1194 for OpenVPN UDP connections.

At the end of the tutorial, you'll have one Access Server pod running in GKE with persistent storage and a stable public IP for VPN and web traffic.

For other Kubernetes platforms, refer to the Kubernetes Helm tutorial. If you'd rather deploy Access Server on a Compute Engine VM instead of Kubernetes, refer to the Google Cloud VM guide.

How the GKE deployment works⁠

The Helm chart creates:

  • One Access Server pod.

  • Persistent storage for Access Server data.

  • A Kubernetes LoadBalancer Service.

  • One public IP for TCP 943, TCP 443, and UDP 1194.

Because the Service contains both TCP and UDP ports, this is a mixed-protocol load balancer.

This tutorial requires GKE version 1.34.1-gke.2190000 or newer. Versions from 1.34.1-gke.2190000 support the external IPv4 mixed-protocol load balancer used in this tutorial.

Prerequisites⁠

Before you begin, ensure you have:

  • A Google Cloud project with billing enabled.

  • Permission to create GKE clusters, Compute Engine addresses, and load balancers in the project.

  • GKE version 1.34.1-gke.2190000 or newer.

  • Google Cloud Shell, or a local machine with:

    • Google Cloud CLI (gcloud).

    • kubectl.

    • Helm.

    • gke-gcloud-auth-plugin.

This tutorial uses Cloud Shell, which provides the required Google Cloud command-line tools.

Limitations and known issues⁠

The following limitations apply when you deploy Access Server from a container image on GKE:

  • Failover mode: This feature isn't supported.

  • Layer 2 bridging: This mode isn't supported.

  • Fixed license keys: This license model isn't supported. Fixed keys can become invalid because a container deployment doesn't provide a persistent hardware fingerprint.

  • Data Channel Offload (DCO): DCO depends on the Linux kernel running on the GKE node. Containers use the host kernel, so DCO must be available there.

    • Linux kernel 6.16 or newer: Access Server 3.2.0 and newer uses the DCO implementation based on upstream Linux kernel 6.16+. If the GKE node kernel includes DCO support, Access Server can use it without an additional module.

    • Linux kernel older than 6.16: Install the DCO module on the Ubuntu GKE node before Access Server can use it. Refer to the DCO tutorial and DCO overview.

  • Performance: The additional container and Kubernetes networking layers can add latency and overhead. Evaluate performance requirements before using this architecture for heavily loaded deployments.

  • PAM authentication: Avoid PAM authentication for this deployment because user credentials stored inside the container aren't persistent.

  • Logs: Access Server sends logs to the container output. Retrieve them with:

    kubectl logs <POD_NAME>

For more information about container deployment behavior, refer to the Docker guide.

Troubleshooting⁠

Submit a support ticket if you need assistance.

Start by checking the pod and Service status:

  1. Check the Access Server pod:

    kubectl get pods
  2. If the pod doesn't start or become ready, describe it:

    kubectl describe pod <POD_NAME>
  3. Review its logs:

    kubectl logs <POD_NAME>
  4. For load balancer problems, inspect the Service and its events:

    kubectl describe svc my-openvpn-as-openvpn-as

Use the following common issues to identify the next step.

Issue

Cause or resolution

ErrImagePull

Kubernetes couldn't retrieve the Access Server container image. Verify the image is available and that outbound network access works.

CrashLoopBackOff

Verify the pod is running with privileged access and can use NET_ADMIN and /dev/net/tun.

LoadBalancerMixedProtocolNotSupported

Ensure the cluster is running GKE 1.34.1-gke.2190000 or newer and that the Service includes cloud.google.com/l4-rbs: "enabled".

Service EXTERNAL-IP stays <pending>

Describe the Service and review its Events section. Verify the regional static IP, GKE version, and backend service-based load balancer configuration.

kubectl get nodes returns No resources found

Verify that you created a GKE Standard cluster rather than an Autopilot cluster and that kubectl is using the intended cluster context.

Pod remains Pending

Verify that the node has sufficient CPU and memory and that the persistent volume can attach.

Pod stays 0/1 Running for more than five minutes

Check the pod events and logs. Verify privileged, NET_ADMIN, and /dev/net/tun access.

Client connects, but the connection profile contains a private IP

Configure the public hostname or regional static IP in Step 8, then issue an updated connection profile as needed.

For additional Google Cloud troubleshooting, refer to the GCP FAQ.