github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/k8s/registry.go (about) 1 package k8s 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "sync" 8 9 "k8s.io/apimachinery/pkg/api/errors" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 apiv1 "k8s.io/client-go/kubernetes/typed/core/v1" 12 13 "github.com/tilt-dev/clusterid" 14 "github.com/tilt-dev/localregistry-go" 15 "github.com/tilt-dev/tilt/internal/container" 16 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 17 "github.com/tilt-dev/tilt/pkg/logger" 18 ) 19 20 // Recommended in Tilt-specific scripts 21 const tiltAnnotationRegistry = "tilt.dev/registry" 22 const tiltAnnotationRegistryFromCluster = "tilt.dev/registry-from-cluster" 23 24 // Recommended in Kind's scripts 25 // https://kind.sigs.k8s.io/docs/user/local-registry/ 26 // There's active work underway to standardize this. 27 const kindAnnotationRegistry = "kind.x-k8s.io/registry" 28 29 const microk8sRegistryNamespace = "container-registry" 30 const microk8sRegistryName = "registry" 31 32 type RuntimeSource interface { 33 Runtime(ctx context.Context) container.Runtime 34 } 35 36 type NaiveRuntimeSource struct { 37 runtime container.Runtime 38 } 39 40 func NewNaiveRuntimeSource(r container.Runtime) NaiveRuntimeSource { 41 return NaiveRuntimeSource{runtime: r} 42 } 43 44 func (s NaiveRuntimeSource) Runtime(ctx context.Context) container.Runtime { 45 return s.runtime 46 } 47 48 type registryAsync struct { 49 env clusterid.Product 50 core apiv1.CoreV1Interface 51 runtimeSource RuntimeSource 52 registry *v1alpha1.RegistryHosting 53 once sync.Once 54 } 55 56 func newRegistryAsync(env clusterid.Product, core apiv1.CoreV1Interface, runtimeSource RuntimeSource) *registryAsync { 57 return ®istryAsync{ 58 env: env, 59 core: core, 60 runtimeSource: runtimeSource, 61 } 62 } 63 64 func (r *registryAsync) inferRegistryFromMicrok8s(ctx context.Context) *v1alpha1.RegistryHosting { 65 // If Microk8s is using the docker runtime, we can just use the microk8s docker daemon 66 // instead of the registry. 67 runtime := r.runtimeSource.Runtime(ctx) 68 if runtime == container.RuntimeDocker { 69 return nil 70 } 71 72 // Microk8s might have a registry enabled. 73 // https://microk8s.io/docs/working 74 svc, err := r.core.Services(microk8sRegistryNamespace).Get(ctx, microk8sRegistryName, metav1.GetOptions{}) 75 if err != nil { 76 if errors.IsNotFound(err) { 77 logger.Get(ctx).Warnf("You are running microk8s without a local image registry.\n" + 78 "Run: `sudo microk8s.enable registry`\n" + 79 "Tilt will use the local registry to speed up builds") 80 } else { 81 logger.Get(ctx).Debugf("Error fetching services: %v", err) 82 } 83 return nil 84 } 85 86 portSpecs := svc.Spec.Ports 87 if len(portSpecs) == 0 { 88 return nil 89 } 90 91 // Check to make sure localhost resolves to an IPv4 address. If it doesn't, 92 // then we won't be able to connect to the registry. See: 93 // https://github.com/tilt-dev/tilt/issues/2369 94 ips, err := net.LookupIP("localhost") 95 if err != nil || len(ips) == 0 || ips[0].To4() == nil { 96 logger.Get(ctx).Warnf("Your /etc/hosts is resolving localhost to ::1 (IPv6).\n" + 97 "This breaks the microk8s image registry.\n" + 98 "Please fix your /etc/hosts to default to IPv4. This will make image pushes much faster.") 99 return nil 100 } 101 102 portSpec := portSpecs[0] 103 host := fmt.Sprintf("localhost:%d", portSpec.NodePort) 104 reg := v1alpha1.RegistryHosting{Host: host} 105 if err := reg.Validate(ctx); err != nil { 106 logger.Get(ctx).Warnf("Error validating private registry host %q: %v", host, err.ToAggregate()) 107 return nil 108 } 109 110 return ® 111 } 112 113 // If this node has the Tilt registry annotations on it, then we can 114 // infer it was set up with a Tilt script and thus has a local registry. 115 func (r *registryAsync) inferRegistryFromNodeAnnotations(ctx context.Context) *v1alpha1.RegistryHosting { 116 nodeList, err := r.core.Nodes().List(ctx, metav1.ListOptions{Limit: 1}) 117 if err != nil || len(nodeList.Items) == 0 { 118 return nil 119 } 120 121 node := nodeList.Items[0] 122 annotations := node.Annotations 123 124 var reg *v1alpha1.RegistryHosting 125 if tiltReg := annotations[tiltAnnotationRegistry]; tiltReg != "" { 126 reg = &v1alpha1.RegistryHosting{ 127 Host: annotations[tiltAnnotationRegistry], 128 HostFromContainerRuntime: annotations[tiltAnnotationRegistryFromCluster], 129 } 130 } else if kindReg := annotations[kindAnnotationRegistry]; kindReg != "" { 131 reg = &v1alpha1.RegistryHosting{Host: kindReg} 132 } 133 134 if reg != nil { 135 if err := reg.Validate(ctx); err != nil { 136 logger.Get(ctx).Warnf("Local registry read from node failed: %v", err.ToAggregate()) 137 return nil 138 } 139 } 140 141 return reg 142 } 143 144 // Implements the local registry discovery standard. 145 func (r *registryAsync) inferRegistryFromConfigMap(ctx context.Context) (registry *v1alpha1.RegistryHosting, help string) { 146 hosting, err := localregistry.Discover(ctx, r.core) 147 if err != nil { 148 logger.Get(ctx).Debugf("Local registry discovery error: %v", err) 149 return nil, "" 150 } 151 152 if hosting.Host == "" { 153 return nil, hosting.Help 154 } 155 156 registry = &v1alpha1.RegistryHosting{ 157 Host: hosting.Host, 158 HostFromClusterNetwork: hosting.HostFromClusterNetwork, 159 HostFromContainerRuntime: hosting.HostFromContainerRuntime, 160 Help: hosting.Help, 161 } 162 163 if err := registry.Validate(ctx); err != nil { 164 logger.Get(ctx).Debugf("Local registry discovery error: %v", err.ToAggregate()) 165 return nil, hosting.Help 166 } 167 return registry, hosting.Help 168 } 169 170 func (r *registryAsync) Registry(ctx context.Context) *v1alpha1.RegistryHosting { 171 r.once.Do(func() { 172 reg, help := r.inferRegistryFromConfigMap(ctx) 173 if !container.IsEmptyRegistry(reg) { 174 r.registry = reg 175 return 176 } 177 178 // Auto-infer the microk8s local registry. 179 if r.env == clusterid.ProductMicroK8s { 180 reg := r.inferRegistryFromMicrok8s(ctx) 181 if !container.IsEmptyRegistry(reg) { 182 r.registry = reg 183 return 184 } 185 } 186 187 reg = r.inferRegistryFromNodeAnnotations(ctx) 188 if !container.IsEmptyRegistry(reg) { 189 r.registry = reg 190 } 191 192 if container.IsEmptyRegistry(r.registry) { 193 if help != "" { 194 logger.Get(ctx).Warnf("You are running without a local image registry.\n"+ 195 "Tilt can use the local registry to speed up builds.\n"+ 196 "Instructions: %s", help) 197 } else if r.env == clusterid.ProductKIND { 198 logger.Get(ctx).Warnf("You are running Kind without a local image registry.\n" + 199 "Tilt can use the local registry to speed up builds.\n" + 200 "Instructions: https://github.com/tilt-dev/kind-local") 201 } 202 } 203 }) 204 return r.registry 205 } 206 207 func (c K8sClient) LocalRegistry(ctx context.Context) *v1alpha1.RegistryHosting { 208 return c.registryAsync.Registry(ctx) 209 }