mirror of https://github.com/kubernetes/kops.git
Merge pull request #5390 from kampka/add-container-proxy
Add pull-through proxy cache for asset docker images
This commit is contained in:
commit
c342df1392
|
@ -555,3 +555,37 @@ spec:
|
||||||
providerExtraConfig:
|
providerExtraConfig:
|
||||||
alias: foo
|
alias: foo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### assets
|
||||||
|
|
||||||
|
Assets define alernative locations from where to retrieve static files and containers
|
||||||
|
|
||||||
|
#### containerRegistry
|
||||||
|
|
||||||
|
The container registry enables kops / kubernets to pull containers from a managed registry.
|
||||||
|
This is useful when pulling containers from the internet is not an option, eg. because the
|
||||||
|
deployment is offline / internet restricted or because of special requirements that apply
|
||||||
|
for deployed artifacts, eg. auditing of containers.
|
||||||
|
|
||||||
|
For a use case example, see [How to use kops in AWS China Region](https://github.com/kubernetes/kops/blob/master/docs/aws-china.md)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
assets:
|
||||||
|
containerRegistry: example.com/registry
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### containerProxy
|
||||||
|
|
||||||
|
The container proxy is designed to acts as a [pull through cache](https://docs.docker.com/registry/recipes/mirror/) for docker container assets.
|
||||||
|
Basically, what it does is it remaps the Kubernets image URL to point to you cache so that the docker daemon will pull the image from that location.
|
||||||
|
If, for example, the containerProxy is set to `proxy.example.com`, the image `k8s.gcr.io/kube-apiserver` will be pulled from `proxy.example.com/kube-apiserver` instead.
|
||||||
|
Note that the proxy you use has to support this feature for private registries.
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
assets:
|
||||||
|
containerProxy: proxy.example.com
|
||||||
|
```
|
||||||
|
|
|
@ -218,6 +218,8 @@ type Assets struct {
|
||||||
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
||||||
// FileRepository is the url for a private file serving repository
|
// FileRepository is the url for a private file serving repository
|
||||||
FileRepository *string `json:"fileRepository,omitempty"`
|
FileRepository *string `json:"fileRepository,omitempty"`
|
||||||
|
// ContainerProxy is a url for a pull-through proxy of a docker registry
|
||||||
|
ContainerProxy *string `json:"containerProxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IAMSpec adds control over the IAM security policies applied to resources
|
// IAMSpec adds control over the IAM security policies applied to resources
|
||||||
|
|
|
@ -217,6 +217,8 @@ type Assets struct {
|
||||||
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
||||||
// FileRepository is the url for a private file serving repository
|
// FileRepository is the url for a private file serving repository
|
||||||
FileRepository *string `json:"fileRepository,omitempty"`
|
FileRepository *string `json:"fileRepository,omitempty"`
|
||||||
|
// ContainerProxy is a url for a pull-through proxy of a docker registry
|
||||||
|
ContainerProxy *string `json:"containerProxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IAMSpec adds control over the IAM security policies applied to resources
|
// IAMSpec adds control over the IAM security policies applied to resources
|
||||||
|
|
|
@ -276,6 +276,7 @@ func Convert_kops_AmazonVPCNetworkingSpec_To_v1alpha1_AmazonVPCNetworkingSpec(in
|
||||||
func autoConvert_v1alpha1_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conversion.Scope) error {
|
func autoConvert_v1alpha1_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conversion.Scope) error {
|
||||||
out.ContainerRegistry = in.ContainerRegistry
|
out.ContainerRegistry = in.ContainerRegistry
|
||||||
out.FileRepository = in.FileRepository
|
out.FileRepository = in.FileRepository
|
||||||
|
out.ContainerProxy = in.ContainerProxy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +288,7 @@ func Convert_v1alpha1_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conv
|
||||||
func autoConvert_kops_Assets_To_v1alpha1_Assets(in *kops.Assets, out *Assets, s conversion.Scope) error {
|
func autoConvert_kops_Assets_To_v1alpha1_Assets(in *kops.Assets, out *Assets, s conversion.Scope) error {
|
||||||
out.ContainerRegistry = in.ContainerRegistry
|
out.ContainerRegistry = in.ContainerRegistry
|
||||||
out.FileRepository = in.FileRepository
|
out.FileRepository = in.FileRepository
|
||||||
|
out.ContainerProxy = in.ContainerProxy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,8 @@ type Assets struct {
|
||||||
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
ContainerRegistry *string `json:"containerRegistry,omitempty"`
|
||||||
// FileRepository is the url for a private file serving repository
|
// FileRepository is the url for a private file serving repository
|
||||||
FileRepository *string `json:"fileRepository,omitempty"`
|
FileRepository *string `json:"fileRepository,omitempty"`
|
||||||
|
// ContainerProxy is a url for a pull-through proxy of a docker registry
|
||||||
|
ContainerProxy *string `json:"containerProxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IAMSpec adds control over the IAM security policies applied to resources
|
// IAMSpec adds control over the IAM security policies applied to resources
|
||||||
|
|
|
@ -290,6 +290,7 @@ func Convert_kops_AmazonVPCNetworkingSpec_To_v1alpha2_AmazonVPCNetworkingSpec(in
|
||||||
func autoConvert_v1alpha2_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conversion.Scope) error {
|
func autoConvert_v1alpha2_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conversion.Scope) error {
|
||||||
out.ContainerRegistry = in.ContainerRegistry
|
out.ContainerRegistry = in.ContainerRegistry
|
||||||
out.FileRepository = in.FileRepository
|
out.FileRepository = in.FileRepository
|
||||||
|
out.ContainerProxy = in.ContainerProxy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,6 +302,7 @@ func Convert_v1alpha2_Assets_To_kops_Assets(in *Assets, out *kops.Assets, s conv
|
||||||
func autoConvert_kops_Assets_To_v1alpha2_Assets(in *kops.Assets, out *Assets, s conversion.Scope) error {
|
func autoConvert_kops_Assets_To_v1alpha2_Assets(in *kops.Assets, out *Assets, s conversion.Scope) error {
|
||||||
out.ContainerRegistry = in.ContainerRegistry
|
out.ContainerRegistry = in.ContainerRegistry
|
||||||
out.FileRepository = in.FileRepository
|
out.FileRepository = in.FileRepository
|
||||||
|
out.ContainerProxy = in.ContainerProxy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,10 @@ func ValidateCluster(c *kops.Cluster, strict bool) *field.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Spec.Assets != nil && c.Spec.Assets.ContainerProxy != nil && c.Spec.Assets.ContainerRegistry != nil {
|
||||||
|
return field.Forbidden(fieldSpec.Child("Assets", "ContainerProxy"), "ContainerProxy cannot be used in conjunction with ContainerRegistry as represent mutually exclusive concepts. Please consult the documentation for details.")
|
||||||
|
}
|
||||||
|
|
||||||
if c.Spec.CloudProvider == "" {
|
if c.Spec.CloudProvider == "" {
|
||||||
return field.Required(fieldSpec.Child("CloudProvider"), "")
|
return field.Required(fieldSpec.Child("CloudProvider"), "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,15 @@ func (in *Assets) DeepCopyInto(out *Assets) {
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.ContainerProxy != nil {
|
||||||
|
in, out := &in.ContainerProxy, &out.ContainerProxy
|
||||||
|
if *in == nil {
|
||||||
|
*out = nil
|
||||||
|
} else {
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
|
@ -17,3 +17,13 @@ go_library(
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["builder_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/apis/kops:go_default_library",
|
||||||
|
"//pkg/apis/kops/util:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
@ -146,6 +147,26 @@ func (a *AssetBuilder) RemapImage(image string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.AssetsLocation != nil && a.AssetsLocation.ContainerProxy != nil {
|
||||||
|
containerProxy := strings.TrimRight(*a.AssetsLocation.ContainerProxy, "/")
|
||||||
|
normalized := image
|
||||||
|
|
||||||
|
// If the image name contains only a single / we need to determine if the image is located on docker-hub or if it's using a convenient URL like k8s.gcr.io/<image-name>
|
||||||
|
// In case of a hub image it should be sufficient to just prepend the proxy url, producing eg docker-proxy.example.com/weaveworks/weave-kube
|
||||||
|
if strings.Count(normalized, "/") <= 1 && !strings.ContainsAny(strings.Split(normalized, "/")[0], ".:") {
|
||||||
|
normalized = containerProxy + "/" + normalized
|
||||||
|
} else {
|
||||||
|
var re = regexp.MustCompile(`^[^/]+`)
|
||||||
|
normalized = re.ReplaceAllString(normalized, containerProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
asset.DockerImage = normalized
|
||||||
|
asset.CanonicalLocation = image
|
||||||
|
|
||||||
|
// Run the new image
|
||||||
|
image = asset.DockerImage
|
||||||
|
}
|
||||||
|
|
||||||
if a.AssetsLocation != nil && a.AssetsLocation.ContainerRegistry != nil {
|
if a.AssetsLocation != nil && a.AssetsLocation.ContainerRegistry != nil {
|
||||||
registryMirror := *a.AssetsLocation.ContainerRegistry
|
registryMirror := *a.AssetsLocation.ContainerRegistry
|
||||||
normalized := image
|
normalized := image
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kops/pkg/apis/kops"
|
||||||
|
"k8s.io/kops/pkg/apis/kops/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildAssetBuilder(t *testing.T) *AssetBuilder {
|
||||||
|
|
||||||
|
builder := &AssetBuilder{
|
||||||
|
AssetsLocation: &kops.Assets{},
|
||||||
|
ContainerAssets: []*ContainerAsset{},
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_RemapImage_ContainerProxy_AppliesToDockerHub(t *testing.T) {
|
||||||
|
builder := buildAssetBuilder(t)
|
||||||
|
|
||||||
|
proxyURL := "proxy.example.com/"
|
||||||
|
image := "weaveworks/weave-kube"
|
||||||
|
expected := "proxy.example.com/weaveworks/weave-kube"
|
||||||
|
|
||||||
|
builder.AssetsLocation.ContainerProxy = &proxyURL
|
||||||
|
|
||||||
|
remapped, err := builder.RemapImage(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error remapping image", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remapped != expected {
|
||||||
|
t.Errorf("Error remapping image (Expecting: %s, got %s)", expected, remapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_RemapImage_ContainerProxy_AppliesToSimplifiedDockerHub(t *testing.T) {
|
||||||
|
builder := buildAssetBuilder(t)
|
||||||
|
|
||||||
|
proxyURL := "proxy.example.com/"
|
||||||
|
image := "debian"
|
||||||
|
expected := "proxy.example.com/debian"
|
||||||
|
|
||||||
|
builder.AssetsLocation.ContainerProxy = &proxyURL
|
||||||
|
|
||||||
|
remapped, err := builder.RemapImage(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error remapping image", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remapped != expected {
|
||||||
|
t.Errorf("Error remapping image (Expecting: %s, got %s)", expected, remapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_RemapImage_ContainerProxy_AppliesToSimplifiedKubernetesURL(t *testing.T) {
|
||||||
|
builder := buildAssetBuilder(t)
|
||||||
|
|
||||||
|
proxyURL := "proxy.example.com/"
|
||||||
|
image := "k8s.gcr.io/kube-apiserver"
|
||||||
|
expected := "proxy.example.com/kube-apiserver"
|
||||||
|
version, _ := util.ParseKubernetesVersion("1.10")
|
||||||
|
|
||||||
|
builder.KubernetesVersion = *version
|
||||||
|
builder.AssetsLocation.ContainerProxy = &proxyURL
|
||||||
|
|
||||||
|
remapped, err := builder.RemapImage(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error remapping image", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remapped != expected {
|
||||||
|
t.Errorf("Error remapping image (Expecting: %s, got %s)", expected, remapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_RemapImage_ContainerProxy_AppliesToLegacyKubernetesURL(t *testing.T) {
|
||||||
|
builder := buildAssetBuilder(t)
|
||||||
|
|
||||||
|
proxyURL := "proxy.example.com/"
|
||||||
|
image := "gcr.io/google_containers/kube-apiserver"
|
||||||
|
expected := "proxy.example.com/google_containers/kube-apiserver"
|
||||||
|
|
||||||
|
builder.AssetsLocation.ContainerProxy = &proxyURL
|
||||||
|
|
||||||
|
remapped, err := builder.RemapImage(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error remapping image", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remapped != expected {
|
||||||
|
t.Errorf("Error remapping image (Expecting: %s, got %s)", expected, remapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_RemapImage_ContainerProxy_AppliesToImagesWithTags(t *testing.T) {
|
||||||
|
builder := buildAssetBuilder(t)
|
||||||
|
|
||||||
|
proxyURL := "proxy.example.com/"
|
||||||
|
image := "k8s.gcr.io/kube-apiserver:1.2.3"
|
||||||
|
expected := "proxy.example.com/kube-apiserver:1.2.3"
|
||||||
|
version, _ := util.ParseKubernetesVersion("1.10")
|
||||||
|
|
||||||
|
builder.KubernetesVersion = *version
|
||||||
|
builder.AssetsLocation.ContainerProxy = &proxyURL
|
||||||
|
|
||||||
|
remapped, err := builder.RemapImage(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error remapping image", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remapped != expected {
|
||||||
|
t.Errorf("Error remapping image (Expecting: %s, got %s)", expected, remapped)
|
||||||
|
}
|
||||||
|
}
|
|
@ -174,6 +174,27 @@ func TestValidate_ClusterName_Import(t *testing.T) {
|
||||||
expectNoErrorFromValidate(t, c)
|
expectNoErrorFromValidate(t, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidate_ContainerRegistry_and_ContainerProxy_exclusivity(t *testing.T) {
|
||||||
|
c := buildDefaultCluster(t)
|
||||||
|
|
||||||
|
assets := new(api.Assets)
|
||||||
|
c.Spec.Assets = assets
|
||||||
|
|
||||||
|
expectNoErrorFromValidate(t, c)
|
||||||
|
|
||||||
|
registry := "https://registry.example.com/"
|
||||||
|
c.Spec.Assets.ContainerRegistry = ®istry
|
||||||
|
expectNoErrorFromValidate(t, c)
|
||||||
|
|
||||||
|
proxy := "https://proxy.example.com/"
|
||||||
|
c.Spec.Assets.ContainerProxy = &proxy
|
||||||
|
expectErrorFromValidate(t, c, "ContainerProxy cannot be used in conjunction with ContainerRegistry")
|
||||||
|
|
||||||
|
c.Spec.Assets.ContainerRegistry = nil
|
||||||
|
expectNoErrorFromValidate(t, c)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func expectErrorFromValidate(t *testing.T, c *api.Cluster, message string) {
|
func expectErrorFromValidate(t *testing.T, c *api.Cluster, message string) {
|
||||||
err := validation.ValidateCluster(c, false)
|
err := validation.ValidateCluster(c, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
Loading…
Reference in New Issue