Set physical machine as Jenkins slave using OpenVPN¶
Imported from Confluence
Content may be outdated. Verify before following any procedures. View original | Last updated: October 2025
Sometimes there is a team in need for a Jenkins slave, which is a physical machine located somewhere. We created a custom setup to achieve this, which consists of the steps described in this section. We will leverage the use of OpenVPN to route traffic between Jenkins and these machines.
Heads up
This setup has configs that we used to set up three mac laptops connected to FairBid Jenkins. If you need to replicate this setup in a different project, please be mindful about replacing the FairBid configurations with the corresponding ones.
Install OpenVPN client¶
The first step would be to make sure that the target machine is permanently connected to OpenVPN. In order to do that, we need to request an OpenVPN package with a profile already embedded. We can ask for it within a CIT ticket. They will:
- Create the host device and generate config
- Define access permissions - who has access to it? By default is not accessible
After getting the package and installing it, we need to make sure that the machine is always connected, therefore we need to adjust the settings so that OpenVPN client automatically connects even on reboot.
Set firewall rule¶
If we want to be able to connect from Kubernetes (where Jenkins is installed) to OpenVPN, we need to set up a firewall rule that allows this connection. It's important to remember that OpenVPN is installed inside Transit VPC, therefore this following rule has to be added to agp-transit-vpc project:
resource "google_compute_firewall" "fairbid-gke-static-subnet-to-openvpn" {
description = null
destination_ranges = []
direction = "INGRESS"
disabled = false
name = "fairbid-gke-static-subnet-to-openvpn"
network = "https://www.googleapis.com/compute/v1/projects/agp-transit-network-prod-lw/global/networks/transit-svpc-prod-01"
priority = 1000
project = "agp-transit-network-prod-lw"
source_ranges = ["172.21.64.0/18"]
source_tags = []
target_tags = ["openvpn"]
allow {
protocol = "all"
}
timeouts {
create = null
delete = null
update = null
}
}
Please note that the source_ranges CIDR block corresponds to the one where Kubernetes pods are created. This example corresponds to FairBid.
The target_tags is openvpn because that's the tag of the VM where OpenVPN is running.
Advertise subnet in project¶
In order to make Jenkins be able to connect to the mac laptops, FairBid needs to know about their IPs. These laptops are connected through OpenVPN to Transit-VPC, so Transit-VPC needs to tell FairBid about their subnet.
The three laptops IPs are:
The smallest subnet that covers them is
Create routes¶
Now we need to configure Transit VPC project and create a route that basically says "every time that we have traffic with destination IP corresponding to the subnet containing the laptops, please send it to the OpenVPN VM". Please note that we have two routes because we have two OpenVPN VMs, for high availability.
resource "google_compute_route" "to-berlin-mac-laptop-1" {
description = "Route destination to macbooks in Berlin office via openvpn agent 1"
dest_range = "100.96.1.128/25"
name = "to-berlin-mac-laptop-1"
network = "https://www.googleapis.com/compute/v1/projects/agp-transit-network-prod-lw/global/networks/transit-svpc-prod-01"
next_hop_gateway = null
next_hop_ilb = null
next_hop_instance = "vm-openvpn-agp-prod-useast1-1"
next_hop_instance_zone = "us-east1-b"
next_hop_ip = null
next_hop_vpn_tunnel = null
priority = 1000
project = "agp-transit-network-prod-lw"
tags = []
timeouts {
create = null
delete = null
}
}
resource "google_compute_route" "to-berlin-mac-laptop-2" {
description = "Route destination to macbooks in Berlin office via openvpn agent 2"
dest_range = "100.96.1.128/25"
name = "to-berlin-mac-laptop-2"
network = "https://www.googleapis.com/compute/v1/projects/agp-transit-network-prod-lw/global/networks/transit-svpc-prod-01"
next_hop_gateway = null
next_hop_ilb = null
next_hop_instance = "vm-openvpn-agp-prod-useast1-2"
next_hop_instance_zone = "us-east1-c"
next_hop_ip = null
next_hop_vpn_tunnel = null
priority = 1000
project = "agp-transit-network-prod-lw"
tags = []
timeouts {
create = null
delete = null
}
}
This config belongs to FairBid project, hence the naming. We have three configured laptops that act as Jenkins slaves. The network containing all them three matches the IP in dest_range and these IPs can be obtained from OpenVPN client UI.
Create DNS¶
Since we don't want to be dealing with the physical machines IPs, the ones assigned by OpenVPN connection, let's create DNS records for them:
"macosx-slave.fyber.com" = {
name = "macosx-slave"
type = "private"
domain = "macosx-slave.fyber.com."
labels = "${local.common_locals.labels}"
recordsets = [
{
name = "intel-1"
type = "A"
ttl = 300
records = [
"100.96.1.162",
]
},
{
name = "m1-1"
type = "A"
ttl = 300
records = [
"100.96.1.178",
]
},
{
name = "m1-2"
type = "A"
ttl = 300
records = [
"100.96.1.194",
]
}
]
}
You can adjust this to your setup, depending on the machines you're preparing, since this block above corresponds to FairBid SDK setup.
We can confirm this works through
nslookup intel-1.macosx-slave.fyber.com
nslookup m1-1.macosx-slave.fyber.com
nslookup m1-2.macosx-slave.fyber.com
The responses should return the OpenVPN IPs.
Prepare physical machine¶
Enable SSH in your physical machine

Tips & tricks¶
Keeping physical machines stable is a real challenge, especially when they're locked up in an office that not everybody has access to. Moreover, during days in which one cannot go to the office but needs to solve some issue, keeping these machines healthy is a must. Here you have some recommendations.
Enable remote login¶
You can do this in a MacOS machine in the settings

Enable "Log in automatically"¶
This will make the system log in once the machine is rebooted, which is essential if the machine restarts and you need the SSH server to be booted up.

Remember
If the user does not log in after rebooting, the SSH server will NOT be started and, therefore, you won't be able to connect
We understand the security concerns of this option but, to mitigate this, enable the lock screen with password after few minutes inactive

which will greatly reduce the exposed risk.
Don't put the machine to sleep¶
Set these options if the machine is connected to power socket and not on battery.

This will prevent killing any session after the machine is inactive for some time.
Set up rebooting schedule¶
Sometimes we adjust some configs on the fly and forget to save them. If we reboot the machine without having written those configurations somewhere, they will be lost.
By rebooting the machine periodically, we ensure that we have a more reliable environment. On top of this, if the machine gets frozen because of whatever reason and we don't have physical access to it, we will just have to wait until next reboot. Combining this with auto-login, guarantees that we will have a more reliable remote connection to it.
Configure Jenkins¶
Now it's the time in which we will add a new agent to Jenkins. This setup assumes that you are using Jenkins Configuration-as-Code.
JCasC:
configScripts:
...
nodes: |
jenkins:
nodes:
- permanent:
labelString: "mac-intel-1"
launcher:
ssh:
credentialsId: "macosx_slave"
host: "100.96.1.162"
javaPath: "/Users/jenkins/.jenv/shims/java"
port: 22
sshHostKeyVerificationStrategy: "nonVerifyingKeyVerificationStrategy"
mode: EXCLUSIVE
name: "macosx_slave_intel_1"
nodeDescription: "MacBook Pro in Berlin office"
numExecutors: 1
remoteFS: "/Users/jenkins"
retentionStrategy:
demand:
idleDelay: 120
inDemandDelay: 1
- permanent:
labelString: "mac-m1-1"
launcher:
ssh:
credentialsId: "macosx_slave"
host: "100.96.1.178"
javaPath: "/Users/jenkins/.jenv/shims/java"
port: 22
sshHostKeyVerificationStrategy: "nonVerifyingKeyVerificationStrategy"
mode: EXCLUSIVE
name: "macosx_slave_m1_1"
nodeDescription: "MacBook Pro in Berlin office"
numExecutors: 1
remoteFS: "/Users/jenkins"
retentionStrategy:
demand:
idleDelay: 120
inDemandDelay: 1
- permanent:
labelString: "mac-m1-2"
launcher:
ssh:
credentialsId: "macosx_slave"
host: "100.96.1.194"
javaPath: "/Users/jenkins/.jenv/shims/java"
port: 22
sshHostKeyVerificationStrategy: "nonVerifyingKeyVerificationStrategy"
mode: EXCLUSIVE
name: "macosx_slave_m1_2"
nodeDescription: "MacBook Pro in Berlin office"
numExecutors: 1
remoteFS: "/Users/jenkins"
retentionStrategy:
demand:
idleDelay: 120
inDemandDelay: 1
...
This assumes that you have an user called jenkins inside your MacOS machine. It also assumes that you're using jenv to manage your Java environment. If this is not the case, please adjust accordingly.
The code above is using some credentials that we need to configure, referred as macosx_slave. In our case, we will use an SSH key:
- The public key will be added to the physical machine and its content will be placed inside
~/.ssh/authorized_keysfile. - The private key will be placed as a secret inside your Jenkins Configuration-as-Code.
JCasC:
configScripts:
...
credentials-conf: |
credentials:
system:
domainCredentials:
- credentials:
- basicSSHUserPrivateKey:
scope: GLOBAL
id: macosx_slave
username: jenkins
privateKeySource:
directEntry:
privateKey: "${MACOSX_SLAVE}"
...
Now just place the content of the private key in a secrets file.
Hey!
The configuration with credentials might be different depending on your setup. In fact, we replaced classic credentials with GCP Secrets connection, but we won't write that whole setup here since it's out of scope.
Enable DNS peering¶
This step is important because the DNS we created above won't be accessible unless we enable DNS peering. This is because the DNS created is private and created under a different project. We should let the Transit VPC know how to resolve it, which can be achieved through DNS peering.
"macosx-slave" = {
"dns_name" = "macosx-slave.fyber.com."
"target_network_url" = "projects/agp-shared-svpc-prod-ew/global/networks/agp-shared-svpc-prod-01"
}
The target_network_url is the network that contains the DNS that OpenVPN should resolve.
You can see an example here.
Configure OpenVPN subnet¶
The last step would be to create a CIT ticket and let them know that the subnet 172.21.0.0/16 should be accessible through OpenVPN. As a reminder, this is the network that contains the Jenkins running pods.