@ -12,6 +12,7 @@ import (
corev1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
"tailscale.com/kube/egressservices"
"tailscale.com/kube/egressservices"
"tailscale.com/types/ptr"
"tailscale.com/types/ptr"
@ -19,21 +20,34 @@ import (
// Returns the base StatefulSet definition for a ProxyGroup. A ProxyClass may be
// Returns the base StatefulSet definition for a ProxyGroup. A ProxyClass may be
// applied over the top after.
// applied over the top after.
func pgStatefulSet ( pg * tsapi . ProxyGroup , namespace , image , tsFirewallMode , cfgHash string ) * appsv1 . StatefulSet {
func pgStatefulSet ( pg * tsapi . ProxyGroup , namespace , image , tsFirewallMode , cfgHash string ) ( * appsv1 . StatefulSet , error ) {
return & appsv1 . StatefulSet {
ss := new ( appsv1 . StatefulSet )
ObjectMeta : metav1 . ObjectMeta {
if err := yaml . Unmarshal ( proxyYaml , & ss ) ; err != nil {
return nil , fmt . Errorf ( "failed to unmarshal proxy spec: %w" , err )
}
// Validate some base assumptions.
if len ( ss . Spec . Template . Spec . InitContainers ) != 1 {
return nil , fmt . Errorf ( "[unexpected] base proxy config had %d init containers instead of 1" , len ( ss . Spec . Template . Spec . InitContainers ) )
}
if len ( ss . Spec . Template . Spec . Containers ) != 1 {
return nil , fmt . Errorf ( "[unexpected] base proxy config had %d containers instead of 1" , len ( ss . Spec . Template . Spec . Containers ) )
}
// StatefulSet config.
ss . ObjectMeta = metav1 . ObjectMeta {
Name : pg . Name ,
Name : pg . Name ,
Namespace : namespace ,
Namespace : namespace ,
Labels : pgLabels ( pg . Name , nil ) ,
Labels : pgLabels ( pg . Name , nil ) ,
OwnerReferences : pgOwnerReference ( pg ) ,
OwnerReferences : pgOwnerReference ( pg ) ,
} ,
}
Spec : appsv1 . StatefulSetSpec {
ss . Spec . Replicas = ptr . To ( pgReplicas ( pg ) )
Replicas : ptr . To ( pgReplicas ( pg ) ) ,
ss . Spec . Selector = & metav1 . LabelSelector {
Selector : & metav1 . LabelSelector {
MatchLabels : pgLabels ( pg . Name , nil ) ,
MatchLabels : pgLabels ( pg . Name , nil ) ,
} ,
}
Template : corev1 . PodTemplateSpec {
ObjectMeta : metav1 . ObjectMeta {
// Template config.
tmpl := & ss . Spec . Template
tmpl . ObjectMeta = metav1 . ObjectMeta {
Name : pg . Name ,
Name : pg . Name ,
Namespace : namespace ,
Namespace : namespace ,
Labels : pgLabels ( pg . Name , nil ) ,
Labels : pgLabels ( pg . Name , nil ) ,
@ -41,37 +55,42 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode, cfgHa
Annotations : map [ string ] string {
Annotations : map [ string ] string {
podAnnotationLastSetConfigFileHash : cfgHash ,
podAnnotationLastSetConfigFileHash : cfgHash ,
} ,
} ,
} ,
}
Spec : corev1 . PodSpec {
tmpl . Spec . ServiceAccountName = pg . Name
ServiceAccountName : pg . Name ,
tmpl . Spec . InitContainers [ 0 ] . Image = image
InitContainers : [ ] corev1 . Container {
tmpl . Spec . Volumes = func ( ) [ ] corev1 . Volume {
{
var volumes [ ] corev1 . Volume
Name : "sysctler" ,
for i := range pgReplicas ( pg ) {
Image : image ,
volumes = append ( volumes , corev1 . Volume {
SecurityContext : & corev1 . SecurityContext {
Name : fmt . Sprintf ( "tailscaledconfig-%d" , i ) ,
Privileged : ptr . To ( true ) ,
VolumeSource : corev1 . VolumeSource {
} ,
Secret : & corev1 . SecretVolumeSource {
Command : [ ] string {
SecretName : fmt . Sprintf ( "%s-%d-config" , pg . Name , i ) ,
"/bin/sh" ,
"-c" ,
} ,
Args : [ ] string {
"sysctl -w net.ipv4.ip_forward=1 && if sysctl net.ipv6.conf.all.forwarding; then sysctl -w net.ipv6.conf.all.forwarding=1; fi" ,
} ,
} ,
} ,
} ,
} ,
Containers : [ ] corev1 . Container {
} )
{
}
Name : "tailscale" ,
Image : image ,
if pg . Spec . Type == tsapi . ProxyGroupTypeEgress {
SecurityContext : & corev1 . SecurityContext {
volumes = append ( volumes , corev1 . Volume {
Capabilities : & corev1 . Capabilities {
Name : pgEgressCMName ( pg . Name ) ,
Add : [ ] corev1 . Capability {
VolumeSource : corev1 . VolumeSource {
"NET_ADMIN" ,
ConfigMap : & corev1 . ConfigMapVolumeSource {
LocalObjectReference : corev1 . LocalObjectReference {
Name : pgEgressCMName ( pg . Name ) ,
} ,
} ,
} ,
} ,
} ,
} ,
VolumeMounts : func ( ) [ ] corev1 . VolumeMount {
} )
}
return volumes
} ( )
// Main container config.
c := & ss . Spec . Template . Spec . Containers [ 0 ]
c . Image = image
c . VolumeMounts = func ( ) [ ] corev1 . VolumeMount {
var mounts [ ] corev1 . VolumeMount
var mounts [ ] corev1 . VolumeMount
for i := range pgReplicas ( pg ) {
for i := range pgReplicas ( pg ) {
mounts = append ( mounts , corev1 . VolumeMount {
mounts = append ( mounts , corev1 . VolumeMount {
@ -88,9 +107,10 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode, cfgHa
ReadOnly : true ,
ReadOnly : true ,
} )
} )
}
}
return mounts
return mounts
} ( ) ,
} ( )
Env : func ( ) [ ] corev1 . EnvVar {
c . Env = func ( ) [ ] corev1 . EnvVar {
envs := [ ] corev1 . EnvVar {
envs := [ ] corev1 . EnvVar {
{
{
// TODO(irbekrm): verify that .status.podIPs are always set, else read in .status.podIP as well.
// TODO(irbekrm): verify that .status.podIPs are always set, else read in .status.podIP as well.
@ -127,12 +147,6 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode, cfgHa
Value : "false" ,
Value : "false" ,
} ,
} ,
}
}
if pg . Spec . Type == tsapi . ProxyGroupTypeEgress {
envs = append ( envs , corev1 . EnvVar {
Name : "TS_EGRESS_SERVICES_CONFIG_PATH" ,
Value : fmt . Sprintf ( "/etc/proxies/%s" , egressservices . KeyEgressServices ) ,
} )
}
if tsFirewallMode != "" {
if tsFirewallMode != "" {
envs = append ( envs , corev1 . EnvVar {
envs = append ( envs , corev1 . EnvVar {
@ -141,41 +155,17 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode, cfgHa
} )
} )
}
}
return envs
} ( ) ,
} ,
} ,
Volumes : func ( ) [ ] corev1 . Volume {
var volumes [ ] corev1 . Volume
for i := range pgReplicas ( pg ) {
volumes = append ( volumes , corev1 . Volume {
Name : fmt . Sprintf ( "tailscaledconfig-%d" , i ) ,
VolumeSource : corev1 . VolumeSource {
Secret : & corev1 . SecretVolumeSource {
SecretName : fmt . Sprintf ( "%s-%d-config" , pg . Name , i ) ,
} ,
} ,
} )
}
if pg . Spec . Type == tsapi . ProxyGroupTypeEgress {
if pg . Spec . Type == tsapi . ProxyGroupTypeEgress {
volumes = append ( volumes , corev1 . Volume {
envs = append ( envs , corev1 . EnvVar {
Name : pgEgressCMName ( pg . Name ) ,
Name : "TS_EGRESS_SERVICES_CONFIG_PATH" ,
VolumeSource : corev1 . VolumeSource {
Value : fmt . Sprintf ( "/etc/proxies/%s" , egressservices . KeyEgressServices ) ,
ConfigMap : & corev1 . ConfigMapVolumeSource {
LocalObjectReference : corev1 . LocalObjectReference {
Name : pgEgressCMName ( pg . Name ) ,
} ,
} ,
} ,
} )
} )
}
}
return volumes
return envs
} ( ) ,
} ( )
} ,
} ,
return ss , nil
} ,
}
}
}
func pgServiceAccount ( pg * tsapi . ProxyGroup , namespace string ) * corev1 . ServiceAccount {
func pgServiceAccount ( pg * tsapi . ProxyGroup , namespace string ) * corev1 . ServiceAccount {