FTP servers are a surprisingly effective way to move data around in Kubernetes, especially when you’re dealing with legacy systems or partners who don’t have direct API access.
Let’s get an vsftpd server up and running, and make sure its data sticks around even if the pod restarts.
First, we need a ConfigMap for vsftpd’s configuration. This is where we’ll set up anonymous access and passive mode.
apiVersion: v1
kind: ConfigMap
metadata:
name: vsftpd-config
data:
vsftpd.conf: |
anonymous_enable=YES
local_enable=NO
write_enable=YES
anon_upload_enable=YES
anon_mkdir_write_enable=YES
chroot_local_user=NO
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
ssl_enable=YES
allow_anon_ssl=YES
force_local_data_ssl=NO
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
ssl_ciphers=HIGH
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=50000
This config enables anonymous access, allows uploads and directory creation for anonymous users, and crucially, sets up passive mode with a defined port range (40000-50000). This port range is essential for clients behind firewalls to connect. We’re also enabling SSL for security, though with self-signed certs for simplicity here.
Next, we’ll define a PersistentVolumeClaim to hold our FTP data. This ensures that any files uploaded will survive pod restarts.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vsftpd-data-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
This PVC requests 5Gi of storage that can be mounted as read-write by a single node. Kubernetes will provision a PersistentVolume (PV) to fulfill this request, typically using your cluster’s default storage class.
Now, let’s create the Deployment for our vsftpd server. This will include the vsftpd container, mount our ConfigMap, and attach the PersistentVolume.
apiVersion: apps/v1
kind: Deployment
metadata:
name: vsftpd-deployment
spec:
replicas: 1
selector:
matchLabels:
app: vsftpd
template:
metadata:
labels:
app: vsftpd
spec:
containers:
- name: vsftpd
image: stilliard/vsftpd # A common vsftpd image
ports:
- containerPort: 21 # FTP control port
- containerPort: 40000 # Passive mode port range start
- containerPort: 50000 # Passive mode port range end
volumeMounts:
- name: config-volume
mountPath: /etc/vsftpd
- name: data-volume
mountPath: /var/ftp
volumes:
- name: config-volume
configMap:
name: vsftpd-config
- name: data-volume
persistentVolumeClaim:
claimName: vsftpd-data-pvc
The Deployment specifies one replica, uses the stilliard/vsftpd image, and exposes ports 21 (control) and the passive mode range (40000-50000). The volumeMounts section connects the vsftpd-config ConfigMap to /etc/vsftpd inside the container and the vsftpd-data-pvc to /var/ftp, which is where vsftpd typically stores its data.
Finally, we expose the FTP service to the outside world with a Service. We need to expose both the control port and the passive port range.
apiVersion: v1
kind: Service
metadata:
name: vsftpd-service
spec:
selector:
app: vsftpd
ports:
- name: control
protocol: TCP
port: 21
targetPort: 21
- name: passive-1
protocol: TCP
port: 40000
targetPort: 40000
- name: passive-2
protocol: TCP
port: 40001
targetPort: 40001
# ... repeat for ports 40002 to 50000 ...
- name: passive-1000
protocol: TCP
port: 50000
targetPort: 50000
type: LoadBalancer
Here, we create a LoadBalancer type service. This will provision an external IP address and forward traffic to our vsftpd pods. Crucially, we’re mapping all ports from 40000 to 50000 to the corresponding targetPorts on the pod. This allows clients to establish passive FTP connections.
With these resources applied (kubectl apply -f ...), you’ll have an anonymous FTP server running in Kubernetes. You can then connect using an FTP client to the external IP address provided by the LoadBalancer service, using anonymous as the username and any email address as the password. Files uploaded will be stored in the vsftpd-data-pvc and will persist across pod restarts.
If you find yourself needing to restrict access or add user authentication, you’ll need to modify the vsftpd.conf in the ConfigMap, potentially change local_enable to YES, and manage user credentials, which often involves mounting a file with hashed passwords or integrating with an external authentication source.
The next hurdle you’ll likely encounter is managing the IP addresses for passive FTP connections when running vsftpd behind a Kubernetes Service of type NodePort or LoadBalancer with dynamic IPs, as the vsftpd server needs to advertise the correct external IP for passive mode.