Built-in Helm Chart¶
The k8s_sandbox
package includes a built-in Helm chart named agent-env
in
resources/helm/
.
About the chart¶
The built-in Helm chart is designed to take a values.yaml
(or helm-values.yaml
) file
which is structured much like a Docker compose.yaml
file.
This is to simplify the complexities of Kubernetes manifests for users who are not familiar with them.
In addition to the info below, see agent-env/README.md
and agent-env/values.yaml
for
a full list of configurable options.
Container runtime (gVisor)¶
The default container runtime class name for every service is gvisor
which, (depending
on your cluster) should map to the runsc
runtime. You can override this if required.
services:
default:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
runtimeClassName: runc
The above example assumes you have a RuntimeClass
named runc
in your cluster which
maps to the runc
runtime.
See the gVisor page for considerations and limitations.
Internet access¶
By default, containers will not be able to access the internet which is an important security measure when running untrusted LLM-generated code. If you wish to allow limited internet access, there are 3 methods, each of which influence the Cilium Network Policy.
-
Populate the
allowDomains
list in yourvalues.yaml
with one or more Fully Qualified Domain Names. The following example list allows agents to install packages from a variety of sources:services: default: image: ubuntu:24.04 command: ["tail", "-f", "/dev/null"] allowDomains: - "pypi.org" - "files.pythonhosted.org" - "bitbucket.org" - "github.com" - "raw.githubusercontent.com" - "*.debian.org" - "*.kali.org" - "kali.download" - "archive.ubuntu.com" - "security.ubuntu.com" - "mirror.vinehost.net" - "*.rubygems.org"
Bug
We're investigating occasional issues with the
allowDomains
feature where some network requests which ought to be allowed are blocked. It is yet unclear whether this is as a result of our service-based DNS resolution setup or whether we're putting Cilium under too much load. -
Populate the
allowCIDR
list with one or more CIDR ranges. These are translated totoCIDRs
entries in the Cilium Network Policy:allowCIDR: - "8.8.8.8/32"
-
Populate the
allowEntities
list with one or more entities. These get translated totoEntities
entries in the Cilium Network Policy:allowEntities: - "world"
DNS¶
The built-in Helm chart is designed to allow services to communicate with each other
using their service names e.g. curl nginx
, much like you would in Docker Compose.
To make services discoverable by their service name, set the dnsRecord
key to true
.
Additionally, you can specify a list of domains that resolve to a given service e.g.
curl example.com
could resolve to your nginx
service.
services:
default:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
nginx:
image: nginx:1.27.2
dnsRecord: true
additionalDnsRecords:
- example.com
To achieve this, whilst maintaining support for deploying multiple instances of the same chart in a given namespace, a CoreDNS "sidecar" container is deployed in each service Pod.
Why not deploy CoreDNS as its own Pod?
Because of the way that Cilium caches DNS responses to determine which IP addresses correspond to the FQDN allowlist, CoreDNS service must be co-located on the same node which the container making the DNS request are on.
Why not use hostAliases
to edit /etc/hosts
?
Instead of using DNS, the /etc/hosts
file could be modified using
HostAliases.
However, some tools which an agent might try to use (e.g. nslookup
) do not respect
/etc/hosts
and will use DNS instead. Therefore, we chose to use a DNS-based
approach.
For the containers within your release to use this, rather than the default Kubernetes
DNS service, the /etc/resolv.conf
of your containers is modified to use 127.0.0.1
as
the nameserver.
Note that the CoreDNS sidecar only binds to 127.0.0.1
so won't be accessible from
outside the Pod.
CoreDNS is used over Dnsmasq for 2 reasons:
- CoreDNS is the default DNS server in Kubernetes.
- It allows you to map domains to known service names whilst delegating resolving the actual IP address to the default Kubernetes DNS service.
Headless services¶
Any entry under the services
key in values.yaml
which sets dnsRecord: true
or
additionalDnsRecords
will have a
headless
Service
created for it.
This creates a DNS record in the Kubernetes cluster which resolves to the Pod's IP
addresses directly. This allows agents to use tools like netcat
or ping
to explore
their environment. If the service were not headless (i.e. it had a ClusterIP), tools
like netcat
would only be able to connect to the ports explicitly exposed by the
service.
Readiness probes¶
Avoid making assumptions about the order in which your containers will start, or the time it will take for them to become ready.
Kubernetes knows when a container has started, but it does not know when the container is ready to accept traffic. In the case of containers such as web servers, there may be a delay between the container starting and the web server being ready to accept traffic.
Use readiness probes as a test to determine if your container is ready to accept traffic. The eval will not begin until all readiness probes have passed.
services:
nginx:
image: nginx:1.27.2
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
You do not need to specify a readinessProbe
on containers which have a trivial
entrypoint (e.g. sleep infinity
or tail -f /dev/null
).
Resource requests and limits¶
Default resource limits are assigned for each service
within the Helm chart. The
limits and requests are equal such that the Pods have a Guaranteed
QoS
class.
resources:
limits:
memory: "2Gi"
cpu: "500m"
requests:
memory: "2Gi"
cpu: "500m"
These can optionally be overridden for each service.
services:
default:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
resources:
limits:
memory: "1Gi"
cpu: "250m"
requests:
memory: "1Gi"
cpu: "250m"
If overriding them, do consider the implications on the QoS class of the Pods and cluster utilization.
Volumes¶
The built-in Helm chart aims to provide a simple way to define and mount volumes in your containers, much like you can in Docker Compose. The following example creates an empty volume which is mounted in both containers.
services:
default:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
volumes:
- "my-volume:/my-volume-mount-path"
worker:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
volumes:
- "my-volume:/my-volume-mount-path"
volumes:
my-volume:
Note that this does not allow you to mount directories from the client system (your machine) into the containers.
This requires that your cluster has a Storage Class named nfs-csi
which supports the
ReadWriteMany
access mode for PersistentVolumeClaim
. See Remote
Cluster.
You can override the spec
of the PersistentVolumeClaim
to suit your needs.
volumes:
my-volume:
spec:
storageClassName: azurefile
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Networks¶
By default, all Pods in a Kubernetes cluster can communicate with each other. Network policies are used to restrict this communication. The built-in Helm chart restricts Pods to only communicate with other Pods in the same Helm release.
Additional restrictions can be put in place, for example, to simulate networks within your eval.
services:
default:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
networks:
- a
intermediate:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
dnsRecord: true
networks:
- a
- b
target:
image: ubuntu:24.04
command: ["tail", "-f", "/dev/null"]
dnsRecord: true
networks:
- b
networks:
a:
b:
In the example above, the default
service can communicate with the intermediate
service, but not the target
service. The intermediate
service can communicate with
the target
service.
When no networks are specified all Pods within a Helm release can communicate with each other. If any networks are specified, any Pod which does not specify a network will not be able to communicate with any other Pod.
Additional resources¶
You can pass arbitrary Kubernetes resources to the Helm chart using the
additionalResources
key in the values.yaml
file.
additionalResources:
- apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
password: my-password
Annotations¶
You can pass arbitrary annotations to the Helm chart using the top-level annotations
key in the values.yaml
file. These will be added as annotations to the Pods, PVCs and
network policies.
The k8s_sandbox
package automatically includes the Inspect task name as an annotation.
annotations:
my-annotation: my-value
This may be useful for determining which task a Pod belongs to.
Render chart without installing¶
helm template ./resources/helm/agent-env > scratch/rendered.yaml
Install chart manually¶
Normally, the Helm chart is installed by the k8s_sandbox
package. However, you can
install it manually which might be useful for debugging.
helm install my-release ./resources/helm/agent-env -f path/to/helm-values.yaml
my-release
is the release name. Consider using your name or initials.
./resources/helm/agent-env
is the path to the Helm chart. If you are outside the
k8s_sandbox
repository, pass the path to the chart in the k8s_sandbox
repository, or
the path to the chart within your virtual environment's k8s_package/
directory.
helm install my-release ~/repos/k8s_sandbox/resources/helm/agent-env -f ...
helm install my-release \
.venv/lib/python3.12/site-packages/k8s_sandbox/resources/helm/agent-env -f ...
Remember to uninstall the release when you are done.
helm uninstall my-release
Generate docs¶
You can regenerate docs using helm-docs after
changing the default values.yaml
.
brew install norwoodj/tap/helm-docs
helm-docs ./resources/helm/agent-env
This is done automatically by the a pre-commit hook.