Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions lib/resourcebuilder/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package resourcebuilder
import (
"context"
"fmt"

"strings"

"k8s.io/klog"

configv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
"github.com/openshift/cluster-version-operator/lib"
"github.com/openshift/cluster-version-operator/lib/resourceapply"
"github.com/openshift/cluster-version-operator/lib/resourceread"
Expand All @@ -17,18 +15,21 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
appsclientv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
"k8s.io/client-go/rest"
"k8s.io/klog"
)

type deploymentBuilder struct {
client *appsclientv1.AppsV1Client
raw []byte
modifier MetaV1ObjectModifierFunc
client *appsclientv1.AppsV1Client
proxyGetter configv1.ProxiesGetter
raw []byte
modifier MetaV1ObjectModifierFunc
}

func newDeploymentBuilder(config *rest.Config, m lib.Manifest) Interface {
return &deploymentBuilder{
client: appsclientv1.NewForConfigOrDie(withProtobuf(config)),
raw: m.Raw,
client: appsclientv1.NewForConfigOrDie(withProtobuf(config)),
proxyGetter: configv1.NewForConfigOrDie(config),
raw: m.Raw,
}
}

Expand All @@ -46,6 +47,26 @@ func (b *deploymentBuilder) Do(ctx context.Context) error {
if b.modifier != nil {
b.modifier(deployment)
}

// if proxy injection is requested, get the proxy values and use them
if containerNamesString := deployment.Annotations["config.openshift.io/inject-proxy"]; len(containerNamesString) > 0 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not terrible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which operators would use this? The ones that need all of their traffic to go through the proxy, including service network (I assume status.noProxy must ALWAYS contain service network or you cause an outage?)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auth operator for example....

the noProxy will be provided by network operator which should set these (I asked him same question during arch call ;-)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image registry operator may use this. We already have similar logic inside of our reconciliation loop. The expectation is that noProxy covers the API server. And our operator needs to interact with external services like S3.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the noProxy will be provided by network operator which should set these

That's correct. I plan to enforce the default noProxy values as part of openshift/cluster-network-operator#245

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danehans the registry operator communicates to cloud apis even when the cluster is not running on that particular cloud.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bparees the registry operator is then responsible for augmenting the cluster-wide noProxy with additional no proxy values, correct? This is the WIP implementation of the cloud metadata ip.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danehans I don't know if that is desirable, particularly if a customer wants their baremetal cluster to have all outbound traffic go through their proxy (and yet still use s3 or other cloud provider storage as their backend).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bparees the registry operator is then responsible for augmenting the cluster-wide noProxy with additional no proxy values, correct?

? My point is that the registry operator has a need to use the proxy configuration when it is talking to the cloudapis that exist outside the cloudprovider the cluster is running on. NoProxy is irrelevant.

@dmage was observing that the registry operator has a use for this proxy env injection. You said they didn't need it because they are talking to cloud apis, but that is incorrect. They are talking to cloudapis, but not cloudapis from the cluster's cloud provider, therefore they will need to go through the proxy (assuming the customer's network topology requires it, which would be our assumption since it's external traffic like any other).

If the customer does not want their AWS api calls going through the proxy (and assuming their network topology doesn't require it), the admin would have to add the AWS api endpoints to their NoProxy value.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if that is desirable, particularly if a customer wants their baremetal cluster to have all outbound traffic go through their proxy (and yet still use s3 or other cloud provider storage as their backend).

@adambkaplan the cloud metadata ip will not be set for vsphere, baremetal and non platform types.

proxyConfig, err := b.proxyGetter.Proxies().Get("cluster", metav1.GetOptions{})
// not found just means that we don't have proxy configuration, so we should tolerate and fill in empty
if err != nil && !errors.IsNotFound(err) {
return err
}
err = updatePodSpecWithProxy(
&deployment.Spec.Template.Spec,
strings.Split(containerNamesString, ","),
proxyConfig.Status.HTTPProxy,
Comment thread
abhinavdahiya marked this conversation as resolved.
proxyConfig.Status.HTTPSProxy,
proxyConfig.Status.NoProxy,
)
if err != nil {
return err
}
}

actual, updated, err := resourceapply.ApplyDeployment(b.client, deployment)
if err != nil {
return err
Expand Down Expand Up @@ -103,15 +124,17 @@ func waitForDeploymentCompletion(ctx context.Context, client appsclientv1.Deploy
}

type daemonsetBuilder struct {
client *appsclientv1.AppsV1Client
raw []byte
modifier MetaV1ObjectModifierFunc
client *appsclientv1.AppsV1Client
proxyGetter configv1.ProxiesGetter
raw []byte
modifier MetaV1ObjectModifierFunc
}

func newDaemonsetBuilder(config *rest.Config, m lib.Manifest) Interface {
return &daemonsetBuilder{
client: appsclientv1.NewForConfigOrDie(withProtobuf(config)),
raw: m.Raw,
client: appsclientv1.NewForConfigOrDie(withProtobuf(config)),
proxyGetter: configv1.NewForConfigOrDie(config),
raw: m.Raw,
}
}

Expand All @@ -129,6 +152,26 @@ func (b *daemonsetBuilder) Do(ctx context.Context) error {
if b.modifier != nil {
b.modifier(daemonset)
}

// if proxy injection is requested, get the proxy values and use them
if containerNamesString := daemonset.Annotations["config.openshift.io/inject-proxy"]; len(containerNamesString) > 0 {
proxyConfig, err := b.proxyGetter.Proxies().Get("cluster", metav1.GetOptions{})
// not found just means that we don't have proxy configuration, so we should tolerate and fill in empty
if err != nil && !errors.IsNotFound(err) {
return err
}
err = updatePodSpecWithProxy(
&daemonset.Spec.Template.Spec,
strings.Split(containerNamesString, ","),
proxyConfig.Status.HTTPProxy,
proxyConfig.Status.HTTPSProxy,
proxyConfig.Status.NoProxy,
)
if err != nil {
return err
}
}

actual, updated, err := resourceapply.ApplyDaemonSet(b.client, daemonset)
if err != nil {
return err
Expand Down
47 changes: 47 additions & 0 deletions lib/resourcebuilder/podspec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package resourcebuilder

import (
"fmt"

corev1 "k8s.io/api/core/v1"
)

// updatePodSpecWithProxy mutates the input podspec with proxy env vars for all init containers and containers
// matching the container names.
func updatePodSpecWithProxy(podSpec *corev1.PodSpec, containerNames []string, httpProxy, httpsProxy, noProxy string) error {
hasProxy := len(httpsProxy) > 0 || len(httpProxy) > 0 || len(noProxy) > 0
Comment thread
abhinavdahiya marked this conversation as resolved.
if !hasProxy {
return nil
}

for _, containerName := range containerNames {
found := false
for i := range podSpec.Containers {
if podSpec.Containers[i].Name != containerName {
continue
}
found = true

podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "HTTP_PROXY", Value: httpProxy})
Comment thread
abhinavdahiya marked this conversation as resolved.
podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "HTTPS_PROXY", Value: httpsProxy})
podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "NO_PROXY", Value: noProxy})
}
for i := range podSpec.InitContainers {
if podSpec.InitContainers[i].Name != containerName {
continue
}
found = true

podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "HTTP_PROXY", Value: httpProxy})
podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "HTTPS_PROXY", Value: httpsProxy})
podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "NO_PROXY", Value: noProxy})
}

if !found {
return fmt.Errorf("requested injection for non-existent container: %q", containerName)
}
}

return nil

}
112 changes: 112 additions & 0 deletions lib/resourcebuilder/podspec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package resourcebuilder

import (
"reflect"
"testing"

"k8s.io/apimachinery/pkg/util/diff"

corev1 "k8s.io/api/core/v1"
)

func TestUpdatePodSpecWithProxy(t *testing.T) {
tests := []struct {
name string

input *corev1.PodSpec
containerNames []string
httpProxy, httpsProxy, noProxy string

expectedErr string
expected *corev1.PodSpec
}{
{
name: "no proxy info",
input: &corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
},
},
},
expected: &corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foo",
},
},
},
},
{
name: "proxy info",
containerNames: []string{"foo", "init-foo"},
httpsProxy: "httpsProxy-val",
noProxy: "noProxy-val",
input: &corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init-foo",
},
{
Name: "init-bar",
},
},
Containers: []corev1.Container{
{
Name: "foo",
},
{
Name: "bar",
},
},
},
expected: &corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "init-foo",
Env: []corev1.EnvVar{
{Name: "HTTP_PROXY", Value: ""},
{Name: "HTTPS_PROXY", Value: "httpsProxy-val"},
{Name: "NO_PROXY", Value: "noProxy-val"},
},
},
{
Name: "init-bar",
},
},
Containers: []corev1.Container{
{
Name: "foo",
Env: []corev1.EnvVar{
{Name: "HTTP_PROXY", Value: ""},
{Name: "HTTPS_PROXY", Value: "httpsProxy-val"},
{Name: "NO_PROXY", Value: "noProxy-val"},
},
},
{
Name: "bar",
},
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := updatePodSpecWithProxy(test.input, test.containerNames, test.httpProxy, test.httpsProxy, test.noProxy)
switch {
case err == nil && len(test.expectedErr) == 0:
case err != nil && len(test.expectedErr) == 0:
t.Fatal(err)
case err == nil && len(test.expectedErr) != 0:
t.Fatal(err)
case err != nil && len(test.expectedErr) != 0 && err.Error() != test.expectedErr:
t.Fatal(err)
}

if !reflect.DeepEqual(test.input, test.expected) {
t.Error(diff.ObjectDiff(test.input, test.expected))
}
})
}
}