DEV Community

Gary Kramlich
Gary Kramlich

Posted on

Tailscale Kubernetes Operator

Apologizes to anyone looking for more #Pidgin content. I know it's been awhile and I'll get something out soon I promise, but there are quite a few Pidgin related things in this post too...

About a month ago I setup a Kubernetes cluster using Talos to handle my container load at home.

I have containers for all sorts of things including the graphs you all may have seen on my stream as well as an Ergo instance for developing our new IRCv3 protocol plugin.

I'll be adding to this cluster in the future too as we continue development on Xeme which is our new XMPP library as well as Myna which is our new Matrix library.

Having these servers running locally makes it easier to test interesting configurations, but more importantly, lets me use them on stream without leaking my IP address or any passwords.

Anyways, I'm not always at home when I want to work on something and I wanted to find a way to access services remotely. Well tonight I realized I should be able to do this with Tailscale.

I'm already using Tailscale to make sure we can access all of our build agents which are spread across a few sites, so being able to expose this to the other Pidgin developers who have access to that Tailscale network is just icing on the cake.

So I decided to give the Tailscale Kubernetes Operator a go. They have full documentation it here. As is pretty typical right now their installation instructions are based on using Helm.

I prefer to use Kustomize over Helm. There's plenty of reasons, but there's no reason to get into that now ;)

Thankfully, Tailscale provides a static manifest for deploying the operator. This is very easy to import into our kustomize and get running directly quite quickly.

But of course, it's not just that easy. Their instructions have you modifying that manifest and then applying that. This isn't a big deal, but it makes upgrading harder as you have to keep track of what you edited. Sure you could use version control (and you should have your manifest in version control) but kustomize makes this very easy.

First we needed to download operator.yaml which is the static manifest that Tailscale provides. We're going to use this file as is without modification which means we can just replace it if/when they update it.

Next we need to create our kustomization.yaml which is used to drive everything. My commented kustomization.yaml is below.

 --- # Here we reference the unmodified operator.yaml we downloaded from # Tailscale. resources: - operator.yaml patches: # Tailscale needs network admin and some other permissions, so # we use the following patch that will add the appropriate * annotations to the namespace. - patch: |- apiVersion: v1 kind: Namespace metadata: name: tailscale labels: pod-security.kubernetes.io/enforce: privileged pod-security.kubernetes.io/audit: privileged pod-security.kubernetes.io/warn: privileged secretGenerator: # As I mentioned earlier, operator.yaml defines a secret for the # tailscale oauth secrets which the documentation wants you to # modify manually. We can instead use a secret generator to read # those values from an `env` file and merge them into the # existing secret. - name: operator-oauth namespace: tailscale behavior: merge envs: - secrets/env 
Enter fullscreen mode Exit fullscreen mode

With this all now setup, we can run kubectl kustomize and verify our output. A condensed output with just the namespace and secret are below.

 apiVersion: v1 kind: Namespace metadata: labels: pod-security.kubernetes.io/audit: privileged pod-security.kubernetes.io/enforce: privileged pod-security.kubernetes.io/warn: privileged name: tailscale --- apiVersion: v1 data: client_id: aGFjayB0aGUgcGxhbmV0 client_secret: aGFja2VycyBvZiB0aGUgd29ybGQgdW5pdGU= kind: Secret metadata: name: operator-oauth namespace: tailscale type: Opaque --- 
Enter fullscreen mode Exit fullscreen mode

Now that we've confirmed everything looks good, we can get ready to apply it to our cluster. Normally I'd recommend doing a dry run first, but since we're applying a namespace as well most of the tests will fail because that namespace doesn't exist.

Anyways, we can apply this kustomization with the following command:

 kubectl apply -k . 
Enter fullscreen mode Exit fullscreen mode

If everything is successful you'll see something like the following:

 namespace/tailscale configured customresourcedefinition.apiextensions.k8s.io/connectors.tailscale.com configured customresourcedefinition.apiextensions.k8s.io/dnsconfigs.tailscale.com configured customresourcedefinition.apiextensions.k8s.io/proxyclasses.tailscale.com configured serviceaccount/operator configured serviceaccount/proxies configured role.rbac.authorization.k8s.io/operator configured role.rbac.authorization.k8s.io/proxies configured clusterrole.rbac.authorization.k8s.io/tailscale-operator configured rolebinding.rbac.authorization.k8s.io/operator configured rolebinding.rbac.authorization.k8s.io/proxies configured clusterrolebinding.rbac.authorization.k8s.io/tailscale-operator configured secret/operator-oauth configured deployment.apps/operator configured ingressclass.networking.k8s.io/tailscale configured 
Enter fullscreen mode Exit fullscreen mode

You can verify the operator is up and running the following command:

 kubectl -n tailscale get deployments 
Enter fullscreen mode Exit fullscreen mode

You should see something like the following:

 NAME READY UP-TO-DATE AVAILABLE AGE operator 1/1 1 1 72m 
Enter fullscreen mode Exit fullscreen mode

If that all looks good you can go ahead and verify that it shows up in the Machines section in Tailscale. You should see something like the following:

Image description

Now we're ready to expose a service via Tailscale!

As I mentioned earlier, I'm running an Ergo instance for local IRC development. I have an Service of type LoadBalancer to expose it to my LAN via MetalLB.

The Tailscale operator has multiple ways to integrate with your Kubernetes cluster, but we're just going to expose existing services to the tailnet and doing that is extremely simple.

To do so, you just need to add the tailscale.com/expose: "true" annotation to the service. Below is my updated Service for Ergo:

 --- apiVersion: v1 kind: Service metadata: name: ergo annotations: tailscale.com/expose: "true" labels: app: ergo spec: ports: - port: 6667 protocol: TCP name: irc - port: 6697 protocol: TCP name: ircs selector: app: ergo type: LoadBalancer 
Enter fullscreen mode Exit fullscreen mode

Now that that's done, I can apply the Kustomization for Ergo:

 kubectl apply -k . 
Enter fullscreen mode Exit fullscreen mode

The operator will automatically add it to the tailnet with a machine name of <namespace>-<service-name>. This ergo instance is running in the reaperworld namespace so it's machine name, and therefore DNS name is reaperworld-ergo which we can now see in the Tailscale machines page.

Image description

And that's it, that service is now available on the tailnet accessible to anyone else on the tailnet!

I hope you all found this interesting. I decided to write this as I had to learn about behavior: merge property of the secretGenerator and figured others might find this useful as well. Plus it gave me an excuse to show the power of Kustomize!

Top comments (0)