Skip to content

Commit

Permalink
Add script to guide developers through setting up their environment t…
Browse files Browse the repository at this point in the history
…o allow locally debugging the Kubernetes Agent (#1007)

* Add initial script with changes

* add comment

* Update setup-k8s-agent-for-local-debug.sh

Co-authored-by: Kevin Tchang <kevin.tchang@octopus.com>

* address code review comments

* Add documentation about debugging in the read me

---------

Co-authored-by: Kevin Tchang <kevin.tchang@octopus.com>
  • Loading branch information
scme0 and kevjt authored Sep 29, 2024
1 parent 081d641 commit e399b64
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 2 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ Currently we can only debug netcore apps running in WSL from VSCode, Visual Stud
"problemMatcher": "$msCompile"
}
```
## Debugging the Kubernetes Agent Tentacle

The Kubernetes Agent Tentacle is a bit more complex to debug, as it normally runs inside a Kubernetes Pod. To debug it locally, you can run the `setup-k8s-agent-for-local-debug.sh` script in the root of this repo which will guide you through the process of installing a specially configured kind cluster, deploying the agent to it and then scaling back the installed agent so you can run a local copy to take it's place.

NOTE: This script has only been tested on MacOS so far and requires Docker Desktop, Kubectl, Go CLI and Kind to be installed. It is also only for the Kubernetes Agent Tentacle running as a Deployment Target, not as a Worker.

## Additional Resources

- Scripts to help with manual testing can be found in [./testing](./testing/README.md).
8 changes: 8 additions & 0 deletions k8s-agent-debug-cluster-config/k8s-agent-debug-cluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: k8s-agent-debug-cluster
nodes:
- role: control-plane
extraMounts:
- hostPath: /tmp/k8s-agent-debug-vol
containerPath: /k8s-agent-debug-vol
28 changes: 28 additions & 0 deletions k8s-agent-debug-cluster-config/launchSettings.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"profiles": {
"Octopus Kubernetes Agent": {
"commandName": "Project",
"commandLineArgs": "agent --instance Tentacle --noninteractive",
"environmentVariables": {
"ACCEPT_EULA": "Y",
"OCTOPUS__K8STENTACLE__NAMESPACE": "octopus-agent-<agent-name>",
"OCTOPUS__K8STENTACLE__PODSERVICEACCOUNTNAME": "octopus-agent-scripts",
"OCTOPUS__K8STENTACLE__PODVOLUMECLAIMNAME": "octopus-agent-<agent-name>-pvc",
"OCTOPUS__K8STENTACLE__HELMRELEASENAME": "<agent-name>",
"OCTOPUS__K8STENTACLE__HELMCHARTVERSION": "2.4.0",
"OCTOPUS__K8STENTACLE__DISABLEAUTOPODCLEANUP": "false",
"OCTOPUS__K8STENTACLE__DISABLEPODEVENTSINTASKLOG": "false",
"OCTOPUS__TENTACLE__LOGLEVEL": "Info",
"TentacleHome": "/tmp/k8s-agent-debug-vol",
"TentacleApplications": "/tmp/k8s-agent-debug-vol/Applications",
"TentaclePollingConnectionCount": "5",
"OCTOPUS__K8STENTACLE__ENABLEMETRICSCAPTURE": "false",
"OCTOPUS__K8STENTACLE__PERSISTENTVOLUMESIZE": "10Gi",
"KUBECONFIG": "<kube-config>",
"DefaultLogDirectory": "/tmp/k8s-agent-debug-vol/logs",
"BOOTSTRAPRUNNEREXECUTABLEPATH": "/tmp/k8s-agent-debug-vol/bootstrapRunner",
"OCTOPUS__K8STENTACLE__PODRESOURCEJSON": "{\"requests\":{\"cpu\":\"25m\",\"memory\":\"100Mi\"}}"
}
}
}
}
21 changes: 21 additions & 0 deletions k8s-agent-debug-cluster-config/local-persistent-volume.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /k8s-agent-debug-vol
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-agent-debug-cluster-control-plane
6 changes: 6 additions & 0 deletions k8s-agent-debug-cluster-config/local-storage-class.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
99 changes: 99 additions & 0 deletions setup-k8s-agent-for-local-debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/bash

echo "This script will create a kind cluster configured for debugging the k8s agent locally in Rider"
echo "NOTE: This has only been tested on MacOS and for agents running as deployment targets."
echo "Requirements: Docker Desktop, Kind CLI, Go CLI, Rider"
echo "continue? Y/n"

ans=Y
read -r ans

if [ -n "$ans" ] && [ "$ans" != "Y" ] && [ "$ans" != "y" ]; then
exit 0
fi

echo ""
echo ""
echo "🏗️ - Installing kind cluster with name \"kind-k8s-agent-debug-cluster\""
kind create cluster --config k8s-agent-debug-cluster-config/k8s-agent-debug-cluster.yaml

if [ $? -ne 0 ]; then
echo ""
echo ""
echo "❗ - Failed to create kind cluster, please check errors above. Aborting."
exit -1
fi

echo "🏗️ - Adding local storage class to kind cluster called \"local-storage\""
kubectl apply -f k8s-agent-debug-cluster-config/local-storage-class.yaml

if [ $? -ne 0 ]; then
echo ""
echo ""
echo "❗ - Failed to create local storage class. Aborting."
exit -1
fi

echo "🏗️ - Adding local persistent volume to kind cluster"
kubectl apply -f k8s-agent-debug-cluster-config/local-persistent-volume.yaml

if [ $? -ne 0 ]; then
echo "❗ - Failed to create local persistent volume. Aborting."
exit -1
fi

echo "🏗️ - Building bootstrap runner"
env GOOS=linux go build -C docker/kubernetes-agent-tentacle/bootstrapRunner -ldflags "-s -w" -o "/tmp/k8s-agent-debug-vol/bootstrapRunner"

if [ $? -ne 0 ]; then
echo ""
echo ""
echo "❗ - Failed to build bootstrap runner. Aborting."
exit -1
fi

echo ""
echo ""
echo "The cluster is ready! 👌"
printf "Now generate the k8s agent helm command from your Octopus instance:\n- ADD \"local-storage\" as the storage class for your agent.\n- CHOOSE \"Docker Desktop\" in the DEV dropdown list.\n- RUN the install script against the new kind cluster (called \"kind-k8s-agent-debug-cluster\").\n\nWhen the installation is complete and the agent has successfully completed a health check, continue executing this script!"

while [ -z "$agentName" ]
do
echo ""
echo ""
echo "❔ - What name did you give your agent?"

read -r agentName
done

echo ""
echo ""
echo "🏗️ - Scaling down agent \"$agentName\" deployment in cluster"
kubectl scale --replicas=0 deployment/octopus-agent-tentacle -n "octopus-agent-$agentName"
echo "🏗️ - Changing the tentacle config to use localhost"
configMapContents=`kubectl get configmap tentacle-config -n "octopus-agent-$agentName" -o yaml`
configMapContents="${configMapContents//host\.docker\.internal/localhost}"
echo "🏗️ - Changing home dir and applications dir to use /tmp/k8s-agent-debug-vol"
configMapContents="${configMapContents//\/octopus//tmp/k8s-agent-debug-vol}"
echo "$configMapContents" | kubectl apply -f -

if [ -z "$KUBECONFIG" ]; then
kubeConfig=~/.kube/config
else
kubeConfig="$KUBECONFIG"
fi

echo ""
echo ""
echo "🏗️ - Copying launch settings for Octopus.Tentacle project"
launchSettings=`cat k8s-agent-debug-cluster-config/launchSettings.template.json`
launchSettings=${launchSettings//\<agent-name\>/"$agentName"}
launchSettings=${launchSettings//\<kube-config\>/"$kubeConfig"}
echo "$launchSettings" > "./source/Octopus.Tentacle/Properties/launchSettings.json"

echo ""
echo ""
echo "Done! 🚀"
echo ""
echo "You can now run/debug the Agent using the launch settings in Octopus.Tentacle/Properties/launchSettings.json."
echo "Check the run configuration in Rider to ensure that you're running it with the correct .net Framework version (net8.0)"
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
using System.Text;
using NLog;
using NLog.LayoutRenderers;
using Octopus.Tentacle.Variables;

namespace Octopus.Tentacle.Diagnostics
{
[LayoutRenderer("octopusLogsDirectory")]
public class OctopusLogsDirectoryRenderer : LayoutRenderer
{
public static readonly string DefaultLogsDirectory =
public static readonly string DefaultLogsDirectory = Environment.GetEnvironmentVariable(EnvironmentVariables.DefaultLogDirectory) ??
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Path.Combine("Octopus", "Logs"));

public static readonly HashSet<string> History = new HashSet<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using Octopus.Diagnostics;
using Octopus.Tentacle.Util;
using Octopus.Tentacle.Variables;

namespace Octopus.Tentacle.Kubernetes
{
Expand All @@ -13,7 +14,7 @@ public class KubernetesPhysicalFileSystem : OctopusPhysicalFileSystem
// Set like this for now because we don't have a way to get the home directory from the provider without requiring ourselves
// DI can be painful when circular dependencies happen with constructed classes :sad-panda:
// When we can get an Injectable KubernetesConfiguration, we can remove this, alternatively, we can pull apart the configuration stores into different implementations
const string HomeDir = "/octopus";
readonly string HomeDir = Environment.GetEnvironmentVariable(EnvironmentVariables.TentacleHome) ?? "/octopus";

public KubernetesPhysicalFileSystem(IKubernetesDirectoryInformationProvider directoryInformationProvider,
ISystemLog log) : base(log)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using k8s;

namespace Octopus.Tentacle.Kubernetes
Expand All @@ -9,6 +10,20 @@ public KubernetesClientConfiguration Get()
{
#if DEBUG
var kubeConfigEnvVar = Environment.GetEnvironmentVariable("KUBECONFIG");
if (kubeConfigEnvVar != null && !Path.IsPathRooted(kubeConfigEnvVar))
{
// Path.GetFullPath doesn't work with ~, so we need to expand it manually
if (kubeConfigEnvVar.StartsWith("~"))
{
kubeConfigEnvVar = kubeConfigEnvVar
.Replace("~", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
.Replace("//", "/");
}
else
{
kubeConfigEnvVar = Path.GetFullPath(kubeConfigEnvVar);
}
}
return KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigEnvVar);
#else
throw new NotSupportedException("Local machine configuration is only supported when debugging.");
Expand Down
1 change: 1 addition & 0 deletions source/Octopus.Tentacle/Variables/EnvironmentVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ public static class EnvironmentVariables
public const string NfsWatchdogDirectory = "watchdog_directory";
public static string TentacleUseTcpNoDelay = "TentacleUseTcpNoDelay";
public static string TentacleUseAsyncListener = "TentacleUseAsyncListener";
public static string DefaultLogDirectory = "DefaultLogDirectory";
}
}

0 comments on commit e399b64

Please sign in to comment.