forked from hashicorp/consul
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request hashicorp#15 from hashicorp/f-envoy
Connect Injector Webhook with Envoy
- Loading branch information
Showing
9 changed files
with
587 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package connectinject | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
func (h *Handler) containerEnvVars(pod *corev1.Pod) []corev1.EnvVar { | ||
raw, ok := pod.Annotations[annotationUpstreams] | ||
if !ok || raw == "" { | ||
return []corev1.EnvVar{} | ||
} | ||
|
||
var result []corev1.EnvVar | ||
for _, raw := range strings.Split(raw, ",") { | ||
parts := strings.SplitN(raw, ":", 2) | ||
port, _ := portValue(pod, strings.TrimSpace(parts[1])) | ||
if port > 0 { | ||
name := strings.TrimSpace(parts[0]) | ||
name = strings.ToUpper(strings.Replace(name, "-", "_", -1)) | ||
portStr := strconv.Itoa(int(port)) | ||
|
||
result = append(result, corev1.EnvVar{ | ||
Name: fmt.Sprintf("%s_CONNECT_SERVICE_HOST", name), | ||
Value: "127.0.0.1", | ||
}, corev1.EnvVar{ | ||
Name: fmt.Sprintf("%s_CONNECT_SERVICE_PORT", name), | ||
Value: portStr, | ||
}) | ||
} | ||
} | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package connectinject | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
"text/template" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
type initContainerCommandData struct { | ||
PodName string | ||
ServiceName string | ||
ServicePort int32 | ||
Upstreams []initContainerCommandUpstreamData | ||
} | ||
|
||
type initContainerCommandUpstreamData struct { | ||
Name string | ||
LocalPort int32 | ||
} | ||
|
||
// containerInit returns the init container spec for registering the Consul | ||
// service, setting up the Envoy bootstrap, etc. | ||
func (h *Handler) containerInit(pod *corev1.Pod) (corev1.Container, error) { | ||
data := initContainerCommandData{ | ||
PodName: pod.Name, | ||
ServiceName: pod.Annotations[annotationService], | ||
} | ||
if data.ServiceName == "" { | ||
// Assertion, since we call defaultAnnotations above and do | ||
// not mutate pods without a service specified. | ||
panic("No service found. This should be impossible since we default it.") | ||
} | ||
|
||
// If a port is specified, then we determine the value of that port | ||
// and register that port for the host service. | ||
if raw, ok := pod.Annotations[annotationPort]; ok && raw != "" { | ||
if port, _ := portValue(pod, raw); port > 0 { | ||
data.ServicePort = port | ||
} | ||
} | ||
|
||
// If upstreams are specified, configure those | ||
if raw, ok := pod.Annotations[annotationUpstreams]; ok && raw != "" { | ||
for _, raw := range strings.Split(raw, ",") { | ||
parts := strings.SplitN(raw, ":", 2) | ||
port, _ := portValue(pod, strings.TrimSpace(parts[1])) | ||
if port > 0 { | ||
data.Upstreams = append(data.Upstreams, initContainerCommandUpstreamData{ | ||
Name: strings.TrimSpace(parts[0]), | ||
LocalPort: port, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
// Render the command | ||
var buf bytes.Buffer | ||
tpl := template.Must(template.New("root").Parse(strings.TrimSpace( | ||
initContainerCommandTpl))) | ||
err := tpl.Execute(&buf, &data) | ||
if err != nil { | ||
return corev1.Container{}, err | ||
} | ||
|
||
return corev1.Container{ | ||
Name: "consul-connect-inject-init", | ||
Image: h.ImageConsul, | ||
Env: []corev1.EnvVar{ | ||
{ | ||
Name: "HOST_IP", | ||
ValueFrom: &corev1.EnvVarSource{ | ||
FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.hostIP"}, | ||
}, | ||
}, | ||
{ | ||
Name: "POD_IP", | ||
ValueFrom: &corev1.EnvVarSource{ | ||
FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, | ||
}, | ||
}, | ||
}, | ||
VolumeMounts: []corev1.VolumeMount{ | ||
corev1.VolumeMount{ | ||
Name: volumeName, | ||
MountPath: "/consul/connect-inject", | ||
}, | ||
}, | ||
Command: []string{"/bin/sh", "-ec", buf.String()}, | ||
}, nil | ||
} | ||
|
||
// initContainerCommandTpl is the template for the command executed by | ||
// the init container. | ||
const initContainerCommandTpl = ` | ||
export CONSUL_HTTP_ADDR="${HOST_IP}:8500" | ||
export CONSUL_GRPC_ADDR="${HOST_IP}:8502" | ||
# Register the service. The HCL is stored in the volume so that | ||
# the preStop hook can access it to deregister the service. | ||
cat <<EOF >/consul/connect-inject/service.hcl | ||
services { | ||
id = "{{ .PodName }}-{{ .ServiceName }}-proxy" | ||
name = "{{ .ServiceName }}-proxy" | ||
kind = "connect-proxy" | ||
address = "${POD_IP}" | ||
port = 20000 | ||
proxy { | ||
destination_service_name = "{{ .ServiceName }}" | ||
destination_service_id = "{{ .ServiceName}}" | ||
{{ if (gt .ServicePort 0) -}} | ||
local_service_address = "127.0.0.1" | ||
local_service_port = {{ .ServicePort }} | ||
{{ end -}} | ||
{{ range .Upstreams -}} | ||
upstreams { | ||
destination_name = "{{ .Name }}" | ||
local_bind_port = {{ .LocalPort }} | ||
} | ||
{{ end }} | ||
} | ||
checks { | ||
name = "Proxy Public Listener" | ||
tcp = "${POD_IP}:20000" | ||
interval = "10s" | ||
deregister_critical_service_after = "10m" | ||
} | ||
checks { | ||
name = "Destination Alias" | ||
alias_service = "{{ .ServiceName }}" | ||
} | ||
} | ||
EOF | ||
/bin/consul services register /consul/connect-inject/service.hcl | ||
# Generate the envoy bootstrap code | ||
/bin/consul connect envoy \ | ||
-proxy-id="{{ .PodName }}-{{ .ServiceName }}-proxy" \ | ||
-bootstrap > /consul/connect-inject/envoy-bootstrap.yaml | ||
# Copy the Consul binary | ||
cp /bin/consul /consul/connect-inject/consul | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package connectinject | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func TestHandlerContainerInit(t *testing.T) { | ||
minimal := func() *corev1.Pod { | ||
return &corev1.Pod{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Annotations: map[string]string{ | ||
annotationService: "foo", | ||
}, | ||
}, | ||
|
||
Spec: corev1.PodSpec{ | ||
Containers: []corev1.Container{ | ||
corev1.Container{ | ||
Name: "web", | ||
}, | ||
|
||
corev1.Container{ | ||
Name: "web-side", | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
cases := []struct { | ||
Name string | ||
Pod func(*corev1.Pod) *corev1.Pod | ||
Cmd string // Strings.Contains test | ||
CmdNot string // Not contains | ||
}{ | ||
{ | ||
"Only service", | ||
func(pod *corev1.Pod) *corev1.Pod { | ||
pod.Annotations[annotationService] = "web" | ||
return pod | ||
}, | ||
`service = "web"`, | ||
`upstreams`, | ||
}, | ||
|
||
{ | ||
"Service port specified", | ||
func(pod *corev1.Pod) *corev1.Pod { | ||
pod.Annotations[annotationService] = "web" | ||
pod.Annotations[annotationPort] = "1234" | ||
return pod | ||
}, | ||
"port = 1234", | ||
"", | ||
}, | ||
|
||
{ | ||
"Upstream", | ||
func(pod *corev1.Pod) *corev1.Pod { | ||
pod.Annotations[annotationService] = "web" | ||
pod.Annotations[annotationUpstreams] = "db:1234" | ||
return pod | ||
}, | ||
`destination_name = "db"`, | ||
"", | ||
}, | ||
} | ||
|
||
for _, tt := range cases { | ||
t.Run(tt.Name, func(t *testing.T) { | ||
require := require.New(t) | ||
|
||
var h Handler | ||
container, err := h.containerInit(tt.Pod(minimal())) | ||
require.NoError(err) | ||
actual := strings.Join(container.Command, " ") | ||
require.Contains(actual, tt.Cmd) | ||
if tt.CmdNot != "" { | ||
require.NotContains(actual, tt.CmdNot) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package connectinject | ||
|
||
import ( | ||
"strings" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
func (h *Handler) containerSidecar(pod *corev1.Pod) corev1.Container { | ||
return corev1.Container{ | ||
Name: "consul-connect-envoy-sidecar", | ||
Image: h.ImageEnvoy, | ||
Env: []corev1.EnvVar{ | ||
{ | ||
Name: "HOST_IP", | ||
ValueFrom: &corev1.EnvVarSource{ | ||
FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.hostIP"}, | ||
}, | ||
}, | ||
}, | ||
VolumeMounts: []corev1.VolumeMount{ | ||
corev1.VolumeMount{ | ||
Name: volumeName, | ||
MountPath: "/consul/connect-inject", | ||
}, | ||
}, | ||
Lifecycle: &corev1.Lifecycle{ | ||
PreStop: &corev1.Handler{ | ||
Exec: &corev1.ExecAction{ | ||
Command: []string{ | ||
"/bin/sh", | ||
"-ec", | ||
strings.TrimSpace(sidecarPreStopCommand), | ||
}, | ||
}, | ||
}, | ||
}, | ||
Command: []string{ | ||
"envoy", | ||
"--config-path", "/consul/connect-inject/envoy-bootstrap.yaml", | ||
}, | ||
} | ||
} | ||
|
||
const sidecarPreStopCommand = ` | ||
export CONSUL_HTTP_ADDR="${HOST_IP}:8500" | ||
/consul/connect-inject/consul services deregister \ | ||
/consul/connect-inject/service.hcl | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package connectinject | ||
|
||
import ( | ||
corev1 "k8s.io/api/core/v1" | ||
) | ||
|
||
// volumeName is the name of the volume that is created to store the | ||
// Consul Connect injection data. | ||
const volumeName = "consul-connect-inject-data" | ||
|
||
// containerVolume returns the volume data to add to the pod. This volume | ||
// is used for shared data between containers. | ||
func (h *Handler) containerVolume() corev1.Volume { | ||
return corev1.Volume{ | ||
Name: volumeName, | ||
VolumeSource: corev1.VolumeSource{ | ||
EmptyDir: &corev1.EmptyDirVolumeSource{}, | ||
}, | ||
} | ||
} |
Oops, something went wrong.