Skip to content

Instantly share code, notes, and snippets.

@manning-ncsa
Created April 18, 2021 17:59
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save manning-ncsa/83e2e49d33976abfadf2b9c51cf36cad to your computer and use it in GitHub Desktop.
Save manning-ncsa/83e2e49d33976abfadf2b9c51cf36cad to your computer and use it in GitHub Desktop.
Syncthing deployment on Kubernetes

Syncthing Device for a Persistent Peer

Overview

Syncthing provides a way to share arbitrary numbers of files between any number of people without the need for a central server. It is a peer-to-peer (P2P) application that automagically syncs files in the background. This deployment app launches a Syncthing "device" (Persistent Peer) that offers a high-availability peer for members of a team to include in their file shares.

For example, imagine there are three people in a team who want to share various folders with each other during the course of their work. They can accomplish this with Syncthing using only their three workstations, by exchanging Device IDs and sharing the folders. However, what if one person modifies or adds a file while the other two are offline, and then that person goes offline before the other two come online? In that event, they would not receive the update. This is where a Persistent Peer is helpful, because in this situation, it is always online to receive updates and then sync them with the other team members when they come online again.

Usage

Team members can add the Persistent Peer Device ID to their individual Syncthing apps on their workstations. Then they will need to login to the web GUI at https://example.com/syncthing and allow the Persistent Peer to accept this connection.

Note

When accepting their offered Device, team members can select "Introducer" and "Auto Accept" so that the Persistent Peer will automatically accept any folder they share with it.

Deployment components

There are two deployments. The first uses a custom-built image from a Dockerfile that essentially just downloads a particular version of Syncthing. The second deployment is an NGINX webserver for use as a reverse-proxy, allowing the web GUI to be accessible at some path of an existing domain (which already has a TLS cert) such as https://example.com/syncthing.

The Persistent Peer configuration files include a config.xml and two files needed for the Persistent Peer identity certificate. The config file was generated automatically in an initial deployment, then customized, and then stored as a ConfigMap that is mounted as a Volume in the init container so that it can be copied in as a file where Syncthing expects it.

The Syncthing GUI is protected at the ingress controller level using Kubernetes annotations on the Ingress defined for the app. The authentication credentials for this HTTP basic auth are stored as a standard Secret.

#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: (@= data.values.app_name @)-reverse-proxy
namespace: #@ data.values.namespace
spec:
selector:
matchLabels:
app: (@= data.values.app_name @)-reverse-proxy
strategy:
type: Recreate
replicas: 1
template:
metadata:
labels:
app: (@= data.values.app_name @)-reverse-proxy
spec:
containers:
- image: nginx:1.19.0
name: (@= data.values.app_name @)-reverse-proxy
imagePullPolicy: IfNotPresent
ports:
- containerPort: #@ data.values.service_port
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: nginx-config
configMap:
name: (@= data.values.app_name @)-reverse-proxy-nginx-config
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: ConfigMap
metadata:
name: (@= data.values.app_name @)-reverse-proxy-nginx-config
data:
default.conf: |
server {
listen 80;
listen [::]:80;
server_name localhost;
location /(@= data.values.webroot @)/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://(@= data.values.app_name @):(@= str(data.values.service_port) @)/;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: Service
metadata:
name: (@= data.values.app_name @)-reverse-proxy
namespace: #@ data.values.namespace
spec:
type: ClusterIP
ports:
- port: 80
selector:
app: (@= data.values.app_name @)-reverse-proxy
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: #@ data.values.app_name
namespace: #@ data.values.namespace
annotations:
kubernetes.io/ingress.class: #@ data.values.ingress_class
# To enable basic HTTP authentication, uncomment the following lines:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: (@= data.values.app_name @)-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required'
spec:
tls:
- hosts:
- #@ data.values.base_domain
secretName: #@ data.values.tls_secret
rules:
- http:
paths:
- path: /(@= data.values.webroot @)
backend:
serviceName: (@= data.values.app_name @)-reverse-proxy
servicePort: 80
host: #@ data.values.base_domain
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: (@= data.values.app_name @)-auth
namespace: #@ data.values.namespace
data:
# user:pass == admin:4...f
auth: YWRt...MQ==
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: ConfigMap
metadata:
name: (@= data.values.app_name @)-certs
data:
cert.pem : |
-----BEGIN CERTIFICATE-----
MI...IU=
-----END CERTIFICATE-----
key.pem : |
-----BEGIN EC PRIVATE KEY-----
MI...OQ=
-----END EC PRIVATE KEY-----
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: (@= data.values.app_name @)
namespace: #@ data.values.namespace
spec:
selector:
matchLabels:
app: (@= data.values.app_name @)
replicas: #@ data.values.replicas
template:
metadata:
labels:
app: (@= data.values.app_name @)
spec:
imagePullSecrets:
- name: registry-auth
initContainers:
- name: init
securityContext:
runAsUser: 0
runAsGroup: 0
image: (@= data.values.app_name @)
imagePullPolicy: IfNotPresent
command:
- '/bin/bash'
- '-c'
- 'bash /opt/start.sh'
volumeMounts:
- name: data
mountPath: /srv/data
- name: config
mountPath: /srv/config
- name: start-script
mountPath: /opt
readOnly: true
- name: syncthing-certs
mountPath: /certs
readOnly: true
containers:
- image: (@= data.values.app_name @)
name: syncthing
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1001
runAsGroup: 1001
command:
- '/bin/bash'
- '-c'
- '/srv/syncthing/syncthing -home=/srv/config'
ports:
- containerPort: #@ data.values.service_port
volumeMounts:
- name: data
mountPath: /srv/data
- name: config
mountPath: /srv/config
volumes:
- name: data
hostPath:
path: (@= data.values.host_root_path @)/(@= data.values.app_name @)/data
- name: config
hostPath:
path: (@= data.values.host_root_path @)/(@= data.values.app_name @)/config
- name: start-script
configMap:
name: (@= data.values.app_name @)-init
- name: syncthing-certs
configMap:
name: (@= data.values.app_name @)-certs
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: Service
metadata:
name: (@= data.values.app_name @)
namespace: #@ data.values.namespace
spec:
type: ClusterIP
ports:
- port: 8080
selector:
app: (@= data.values.app_name @)
#@ load("@ytt:data", "data")
#@yaml/text-templated-strings
---
apiVersion: v1
kind: ConfigMap
metadata:
name: (@= data.values.app_name @)-init
data:
start.sh: |
#!/bin/bash
# strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
# If this is a fresh deployment, copy in the config and cert
if [ ! -f /srv/config/config.xml ]; then
cp /opt/syncthing.config.xml /srv/config/config.xml
cp /certs/*.pem /srv/config/
fi
# Set permissions so that we have access to volumes
chown -R 1001:1001 /srv/config /srv/data
chmod -R 700 /srv/config /srv/data
syncthing.config.xml: |
<configuration version="32">
<device id="5QZ7GPD-...-3MJR3QT" name="thingamabob" compression="metadata" introducer="false" skipIntroductionRemovals="false" introducedBy="">
...
</configuration>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment