pkg/inject/envoy.go (295 lines of code) (raw):
package inject
import (
"fmt"
"k8s.io/apimachinery/pkg/util/json"
"strconv"
"strings"
appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2"
"github.com/aws/aws-sdk-go/aws"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
)
const envoyContainerName = "envoy"
type envoyMutatorConfig struct {
accountID string
awsRegion string
preview bool
enableSDS bool
sdsUdsPath string
logLevel string
adminAccessPort int32
adminAccessLogFile string
preStopDelay string
postStartTimeout int32
postStartInterval int32
readinessProbeInitialDelay int32
readinessProbePeriod int32
sidecarImageRepository string
sidecarImageTag string
sidecarCPURequests string
sidecarMemoryRequests string
sidecarCPULimits string
sidecarMemoryLimits string
enableXrayTracing bool
xrayDaemonPort int32
xraySamplingRate string
enableJaegerTracing bool
jaegerPort string
jaegerAddress string
enableDatadogTracing bool
datadogTracerPort int32
datadogTracerAddress string
enableStatsTags bool
enableStatsD bool
statsDPort int32
statsDAddress string
statsDSocketPath string
waitUntilProxyReady bool
controllerVersion string
k8sVersion string
useDualStackEndpoint bool
enableAdminAccessIPv6 bool
useFipsEndpoint bool
awsAccessKeyId string
awsSecretAccessKey string
awsSessionToken string
}
func newEnvoyMutator(mutatorConfig envoyMutatorConfig, ms *appmesh.Mesh, vn *appmesh.VirtualNode) *envoyMutator {
return &envoyMutator{
vn: vn,
ms: ms,
mutatorConfig: mutatorConfig,
}
}
type envoyMutator struct {
vn *appmesh.VirtualNode
ms *appmesh.Mesh
mutatorConfig envoyMutatorConfig
}
func (m *envoyMutator) mutate(pod *corev1.Pod) error {
if ok, _ := containsEnvoyContainer(pod); ok {
return nil
}
secretMounts, err := m.getSecretMounts(pod)
if err != nil {
return err
}
volumeMounts, err := m.getVolumeMounts(pod)
if err != nil {
return err
}
variables := m.buildTemplateVariables(pod)
customEnv, err := m.getCustomEnv(pod)
if err != nil {
return err
}
customEnvJson, err := m.getCustomEnvJson(pod)
if err != nil {
return err
}
if customEnvJson != nil {
for k, v := range customEnvJson {
customEnv[k] = v
}
}
container, err := buildEnvoySidecar(variables, customEnv)
if err != nil {
return err
}
// add resource requests and limits
container.Resources, err = sidecarResources(getSidecarCPURequest(m.mutatorConfig.sidecarCPURequests, pod),
getSidecarMemoryRequest(m.mutatorConfig.sidecarMemoryRequests, pod),
getSidecarCPULimit(m.mutatorConfig.sidecarCPULimits, pod),
getSidecarMemoryLimit(m.mutatorConfig.sidecarMemoryLimits, pod))
if err != nil {
return err
}
// add readiness probe
container.ReadinessProbe = envoyReadinessProbe(m.mutatorConfig.readinessProbeInitialDelay,
m.mutatorConfig.readinessProbePeriod, strconv.Itoa(int(m.mutatorConfig.adminAccessPort)))
m.mutateSecretMounts(pod, &container, secretMounts)
m.mutateVolumeMounts(pod, &container, volumeMounts)
if m.mutatorConfig.enableSDS && !isSDSDisabled(pod) {
mutateSDSMounts(pod, &container, m.mutatorConfig.sdsUdsPath)
}
// waitUntilProxyReady requires starting sidecar container first
if m.mutatorConfig.waitUntilProxyReady {
pod.Spec.Containers = append([]corev1.Container{container}, pod.Spec.Containers...)
} else {
pod.Spec.Containers = append(pod.Spec.Containers, container)
}
return nil
}
func (m *envoyMutator) buildTemplateVariables(pod *corev1.Pod) EnvoyTemplateVariables {
meshName := m.getAugmentedMeshName()
virtualNodeName := aws.StringValue(m.vn.Spec.AWSName)
preview := m.getPreview(pod)
useDualStackEndpoint := m.getUseDualStackEndpoint(m.mutatorConfig.useDualStackEndpoint)
useFipsEndpoint := m.getUseFipsEndpoint(m.mutatorConfig.useFipsEndpoint)
sdsEnabled := m.mutatorConfig.enableSDS
if m.mutatorConfig.enableSDS && isSDSDisabled(pod) {
sdsEnabled = false
}
return EnvoyTemplateVariables{
AWSRegion: m.mutatorConfig.awsRegion,
MeshName: meshName,
VirtualGatewayOrNodeName: virtualNodeName,
Preview: preview,
EnableSDS: sdsEnabled,
SdsUdsPath: m.mutatorConfig.sdsUdsPath,
LogLevel: m.mutatorConfig.logLevel,
AdminAccessPort: m.mutatorConfig.adminAccessPort,
AdminAccessLogFile: m.mutatorConfig.adminAccessLogFile,
PreStopDelay: m.mutatorConfig.preStopDelay,
PostStartTimeout: m.mutatorConfig.postStartTimeout,
PostStartInterval: m.mutatorConfig.postStartInterval,
SidecarImageRepository: m.mutatorConfig.sidecarImageRepository,
SidecarImageTag: m.mutatorConfig.sidecarImageTag,
EnableXrayTracing: m.mutatorConfig.enableXrayTracing,
XrayDaemonPort: m.mutatorConfig.xrayDaemonPort,
XraySamplingRate: m.mutatorConfig.xraySamplingRate,
EnableJaegerTracing: m.mutatorConfig.enableJaegerTracing,
JaegerPort: m.mutatorConfig.jaegerPort,
JaegerAddress: m.mutatorConfig.jaegerAddress,
EnableDatadogTracing: m.mutatorConfig.enableDatadogTracing,
DatadogTracerPort: m.mutatorConfig.datadogTracerPort,
DatadogTracerAddress: m.mutatorConfig.datadogTracerAddress,
EnableStatsTags: m.mutatorConfig.enableStatsTags,
EnableStatsD: m.mutatorConfig.enableStatsD,
StatsDPort: m.mutatorConfig.statsDPort,
StatsDAddress: m.mutatorConfig.statsDAddress,
StatsDSocketPath: m.mutatorConfig.statsDSocketPath,
ControllerVersion: m.mutatorConfig.controllerVersion,
K8sVersion: m.mutatorConfig.k8sVersion,
UseDualStackEndpoint: useDualStackEndpoint,
EnableAdminAccessForIpv6: m.mutatorConfig.enableAdminAccessIPv6,
WaitUntilProxyReady: m.mutatorConfig.waitUntilProxyReady,
UseFipsEndpoint: useFipsEndpoint,
AwsAccessKeyId: m.mutatorConfig.awsAccessKeyId,
AwsSecretAccessKey: m.mutatorConfig.awsSecretAccessKey,
AwsSessionToken: m.mutatorConfig.awsSessionToken,
}
}
func (m *envoyMutator) getAugmentedMeshName() string {
meshName := aws.StringValue(m.ms.Spec.AWSName)
if m.ms.Spec.MeshOwner != nil && aws.StringValue(m.ms.Spec.MeshOwner) != m.mutatorConfig.accountID {
return fmt.Sprintf("%v@%v", meshName, aws.StringValue(m.ms.Spec.MeshOwner))
}
return meshName
}
func (m *envoyMutator) getPreview(pod *corev1.Pod) string {
preview := m.mutatorConfig.preview
if v, ok := pod.ObjectMeta.Annotations[AppMeshPreviewAnnotation]; ok {
preview = strings.ToLower(v) == "enabled"
}
if preview {
return "1"
}
return "0"
}
func (m *envoyMutator) mutateSecretMounts(pod *corev1.Pod, envoyContainer *corev1.Container, secretMounts map[string]string) {
for secretName, mountPath := range secretMounts {
volume := corev1.Volume{
Name: secretName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
},
},
}
volumeMount := corev1.VolumeMount{
Name: secretName,
MountPath: mountPath,
ReadOnly: true,
}
envoyContainer.VolumeMounts = append(envoyContainer.VolumeMounts, volumeMount)
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
}
}
func (m *envoyMutator) getSecretMounts(pod *corev1.Pod) (map[string]string, error) {
secretMounts := make(map[string]string)
if v, ok := pod.ObjectMeta.Annotations[AppMeshSecretMountsAnnotation]; ok {
for _, segment := range strings.Split(v, ",") {
pair := strings.Split(segment, ":")
if len(pair) != 2 { // secretName:mountPath
return nil, errors.Errorf("malformed annotation %s, expected format: %s", AppMeshSecretMountsAnnotation, "secretName:mountPath")
}
secretName := strings.TrimSpace(pair[0])
mountPath := strings.TrimSpace(pair[1])
secretMounts[secretName] = mountPath
}
}
return secretMounts, nil
}
func (m *envoyMutator) getCustomEnv(pod *corev1.Pod) (map[string]string, error) {
customEnv := make(map[string]string)
if v, ok := pod.ObjectMeta.Annotations[AppMeshEnvAnnotation]; ok {
for _, segment := range strings.Split(v, ",") {
pair := strings.Split(segment, "=")
if len(pair) != 2 { // EnvVariableKey=EnvVariableValue
return nil, errors.Errorf("malformed annotation %s, expected format: %s", AppMeshEnvAnnotation, "EnvVariableKey=EnvVariableValue")
}
envKey := strings.TrimSpace(pair[0])
envVal := strings.TrimSpace(pair[1])
customEnv[envKey] = envVal
}
}
return customEnv, nil
}
func (m *envoyMutator) getCustomEnvJson(pod *corev1.Pod) (map[string]string, error) {
var temp []map[string]interface{}
customEnvJson := make(map[string]string)
if v, ok := pod.ObjectMeta.Annotations[AppMeshEnvJsonAnnotation]; ok {
err := json.Unmarshal([]byte(v), &temp)
for _, item := range temp {
for key, value := range item {
if strValue, isString := value.(string); isString {
customEnvJson[key] = strValue
} else {
return nil, errors.Errorf("nested json isn't supported with this annotation %s, expected format: %s", AppMeshEnvJsonAnnotation, `[{"DD_ENV":"prod","TEST_ENV":"env_val"}]`)
}
}
}
if err != nil {
err = errors.Errorf("malformed annotation %s, expected format: %s", AppMeshEnvJsonAnnotation, `[{"DD_ENV":"prod","TEST_ENV":"env_val"}]`)
return nil, err
}
}
return customEnvJson, nil
}
func (m *envoyMutator) mutateVolumeMounts(pod *corev1.Pod, envoyContainer *corev1.Container, volumeMounts map[string]string) {
for volumeName, mountPath := range volumeMounts {
volumeMount := corev1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
ReadOnly: true,
}
envoyContainer.VolumeMounts = append(envoyContainer.VolumeMounts, volumeMount)
}
}
func (m *envoyMutator) getVolumeMounts(pod *corev1.Pod) (map[string]string, error) {
volumeMounts := make(map[string]string)
if v, ok := pod.ObjectMeta.Annotations[AppMeshVolumeMountsAnnotation]; ok {
for _, segment := range strings.Split(v, ",") {
pair := strings.Split(segment, ":")
if len(pair) != 2 { // volumeName:mountPath
return nil, errors.Errorf("malformed annotation %s, expected format: %s", AppMeshVolumeMountsAnnotation, "volumeName:mountPath")
}
volumeName := strings.TrimSpace(pair[0])
mountPath := strings.TrimSpace(pair[1])
volumeMounts[volumeName] = mountPath
}
}
return volumeMounts, nil
}
func (m *envoyMutator) getUseDualStackEndpoint(useDualStackEndpoint bool) string {
if useDualStackEndpoint {
return "1"
} else {
return "0"
}
}
func (m *envoyMutator) getUseFipsEndpoint(useFipsEndpoint bool) string {
if useFipsEndpoint {
return "1"
} else {
return "0"
}
}