Set container env var using services. (#573)

* Introduce service env vars.
This commit is contained in:
Yash Desai
2019-04-17 11:30:39 -07:00
committed by Brian Goff
parent 6cb323eec2
commit de32752395
10 changed files with 1389 additions and 5 deletions

77
vkubelet/env.go Normal file → Executable file
View File

@@ -8,10 +8,14 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
apivalidation "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/tools/record"
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
fieldpath "k8s.io/kubernetes/pkg/fieldpath"
"k8s.io/kubernetes/pkg/kubelet/envvars"
"github.com/virtual-kubelet/virtual-kubelet/log"
"github.com/virtual-kubelet/virtual-kubelet/manager"
@@ -50,9 +54,12 @@ const (
ReasonInvalidEnvironmentVariableNames = "InvalidEnvironmentVariableNames"
)
var masterServices = sets.NewString("kubernetes")
// populateEnvironmentVariables populates the environment of each container (and init container) in the specified pod.
// TODO Make this the single exported function of a "pkg/environment" package in the future.
func populateEnvironmentVariables(ctx context.Context, pod *corev1.Pod, rm *manager.ResourceManager, recorder record.EventRecorder) error {
// Populate each init container's environment.
for idx := range pod.Spec.InitContainers {
if err := populateContainerEnvironment(ctx, pod, &pod.Spec.InitContainers[idx], rm, recorder); err != nil {
@@ -89,6 +96,53 @@ func populateContainerEnvironment(ctx context.Context, pod *corev1.Pod, containe
return nil
}
// getServiceEnvVarMap makes a map[string]string of env vars for services a
// pod in namespace ns should see.
// Based on getServiceEnvVarMap in kubelet_pods.go.
func getServiceEnvVarMap(rm *manager.ResourceManager, ns string, enableServiceLinks bool) (map[string]string, error) {
var (
serviceMap = make(map[string]*corev1.Service)
m = make(map[string]string)
)
services, err := rm.ListServices()
if err != nil {
return nil, err
}
// project the services in namespace ns onto the master services
for i := range services {
service := services[i]
// ignore services where ClusterIP is "None" or empty
if !v1helper.IsServiceIPSet(service) {
continue
}
serviceName := service.Name
// We always want to add environment variables for master kubernetes service
// from the default namespace, even if enableServiceLinks is false.
// We also add environment variables for other services in the same
// namespace, if enableServiceLinks is true.
if service.Namespace == metav1.NamespaceDefault && masterServices.Has(serviceName) {
if _, exists := serviceMap[serviceName]; !exists {
serviceMap[serviceName] = service
}
} else if service.Namespace == ns && enableServiceLinks {
serviceMap[serviceName] = service
}
}
mappedServices := make([]*corev1.Service, 0, len(serviceMap))
for key := range serviceMap {
mappedServices = append(mappedServices, serviceMap[key])
}
for _, e := range envvars.FromServices(mappedServices) {
m[e.Name] = e.Value
}
return m, nil
}
// makeEnvironmentMapBasedOnEnvFrom returns a map representing the resolved environment of the specified container after being populated from the entries in the ".envFrom" field.
func makeEnvironmentMapBasedOnEnvFrom(ctx context.Context, pod *corev1.Pod, container *corev1.Container, rm *manager.ResourceManager, recorder record.EventRecorder) (map[string]string, error) {
// Create a map to hold the resulting environment.
@@ -347,6 +401,29 @@ loop:
continue loop
}
}
// TODO If pod.Spec.EnableServiceLinks is nil then fail as per 1.14 kubelet.
enableServiceLinks := corev1.DefaultEnableServiceLinks
if pod.Spec.EnableServiceLinks != nil {
enableServiceLinks = *pod.Spec.EnableServiceLinks
}
// Note that there is a race between Kubelet seeing the pod and kubelet seeing the service.
// To avoid this users can: (1) wait between starting a service and starting; or (2) detect
// missing service env var and exit and be restarted; or (3) use DNS instead of env vars
// and keep trying to resolve the DNS name of the service (recommended).
svcEnv, err := getServiceEnvVarMap(rm, pod.Namespace, enableServiceLinks)
if err != nil {
return nil, err
}
// Append service env vars.
for k, v := range svcEnv {
if _, present := res[k]; !present {
res[k] = v
}
}
// Return the populated environment.
return res, nil
}

View File

@@ -192,6 +192,7 @@ func TestPopulatePodWithInitContainersUsingEnv(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -351,6 +352,7 @@ func TestPopulatePodWithInitContainersUsingEnvWithFieldRef(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -466,6 +468,7 @@ func TestPopulatePodWithInitContainersUsingEnvFrom(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -544,6 +547,7 @@ func TestEnvFromTwoConfigMapsAndOneSecret(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -605,6 +609,7 @@ func TestEnvFromConfigMapAndSecretWithInvalidKeys(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -675,6 +680,7 @@ func TestEnvOverridesEnvFrom(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -751,6 +757,7 @@ func TestEnvFromInexistentConfigMaps(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -807,6 +814,7 @@ func TestEnvFromInexistentSecrets(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -859,6 +867,7 @@ func TestEnvReferencingInexistentConfigMapKey(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -908,6 +917,7 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
},
},
},
EnableServiceLinks: &bFalse,
},
}
@@ -921,3 +931,83 @@ func TestEnvReferencingInexistentSecretKey(t *testing.T) {
assert.Check(t, is.Contains(event1, ReasonMandatorySecretNotFound))
assert.Check(t, is.Contains(event1, missingSecretName))
}
// TestServiceEnvVar tries to populate the environment of a container using services with ServiceLinks enabled and disabled.
func TestServiceEnvVar(t *testing.T) {
namespace2 := "namespace-02"
service1 := testutil.FakeService(metav1.NamespaceDefault, "kubernetes", "1.2.3.1", "TCP", 8081)
service2 := testutil.FakeService(namespace, "test", "1.2.3.3", "TCP", 8083)
// unused svc to show it isn't populated within a different namespace.
service3 := testutil.FakeService(namespace2, "unused", "1.2.3.4", "TCP", 8084)
rm := testutil.FakeResourceManager(service1, service2, service3)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "test-pod-name",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
},
},
},
},
}
testCases := []struct {
name string // the name of the test case
enableServiceLinks *bool // enabling service links
expectedEnvs []corev1.EnvVar // a set of expected environment vars
}{
{
name: "ServiceLinks disabled",
enableServiceLinks: &bFalse,
expectedEnvs: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
},
},
{
name: "ServiceLinks enabled",
enableServiceLinks: &bTrue,
expectedEnvs: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
{Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"},
{Name: "TEST_SERVICE_PORT", Value: "8083"},
{Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"},
{Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"},
{Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"},
{Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"},
{Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"},
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
},
},
}
for _, tc := range testCases {
pod.Spec.EnableServiceLinks = tc.enableServiceLinks
err := populateEnvironmentVariables(context.Background(), pod, rm, er)
assert.NilError(t, err, "[%s]", tc.name)
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, tc.expectedEnvs, sortOpt))
}
}