Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
65
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/BUILD
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"dns_test.go",
|
||||
"versions_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"dns.go",
|
||||
"manifests.go",
|
||||
"versions.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
147
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/dns.go
generated
vendored
Normal file
147
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/dns.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
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 dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kuberuntime "k8s.io/apimachinery/pkg/runtime"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// KubeDNSServiceAccountName describes the name of the ServiceAccount for the kube-dns addon
|
||||
KubeDNSServiceAccountName = "kube-dns"
|
||||
)
|
||||
|
||||
// EnsureDNSAddon creates the kube-dns addon
|
||||
func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
|
||||
k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err)
|
||||
}
|
||||
|
||||
if err := CreateServiceAccount(client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the YAML manifest conditionally based on the k8s version
|
||||
kubeDNSDeploymentBytes := GetKubeDNSManifest(k8sVersion)
|
||||
dnsDeploymentBytes, err := kubeadmutil.ParseTemplate(kubeDNSDeploymentBytes, struct{ ImageRepository, Arch, Version, DNSDomain, MasterTaintKey string }{
|
||||
ImageRepository: cfg.ImageRepository,
|
||||
Arch: runtime.GOARCH,
|
||||
// Get the kube-dns version conditionally based on the k8s version
|
||||
Version: GetKubeDNSVersion(k8sVersion),
|
||||
DNSDomain: cfg.Networking.DNSDomain,
|
||||
MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing kube-dns deployment template: %v", err)
|
||||
}
|
||||
|
||||
dnsip, err := getDNSIP(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dnsServiceBytes, err := kubeadmutil.ParseTemplate(KubeDNSService, struct{ DNSIP string }{
|
||||
DNSIP: dnsip.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing kube-proxy configmap template: %v", err)
|
||||
}
|
||||
|
||||
if err := createKubeDNSAddon(dnsDeploymentBytes, dnsServiceBytes, client); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("[addons] Applied essential addon: kube-dns")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateServiceAccount creates the necessary serviceaccounts that kubeadm uses/might use, if they don't already exist.
|
||||
func CreateServiceAccount(client clientset.Interface) error {
|
||||
|
||||
return apiclient.CreateOrUpdateServiceAccount(client, &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: KubeDNSServiceAccountName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.Interface) error {
|
||||
kubednsDeployment := &extensions.Deployment{}
|
||||
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), deploymentBytes, kubednsDeployment); err != nil {
|
||||
return fmt.Errorf("unable to decode kube-dns deployment %v", err)
|
||||
}
|
||||
|
||||
// Create the Deployment for kube-dns or update it in case it already exists
|
||||
if err := apiclient.CreateOrUpdateDeployment(client, kubednsDeployment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubednsService := &v1.Service{}
|
||||
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), serviceBytes, kubednsService); err != nil {
|
||||
return fmt.Errorf("unable to decode kube-dns service %v", err)
|
||||
}
|
||||
|
||||
// Can't use a generic apiclient helper func here as we have to tolerate more than AlreadyExists.
|
||||
if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(kubednsService); err != nil {
|
||||
// Ignore if the Service is invalid with this error message:
|
||||
// Service "kube-dns" is invalid: spec.clusterIP: Invalid value: "10.96.0.10": provided IP is already allocated
|
||||
|
||||
if !apierrors.IsAlreadyExists(err) && !apierrors.IsInvalid(err) {
|
||||
return fmt.Errorf("unable to create a new kube-dns service: %v", err)
|
||||
}
|
||||
|
||||
if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(kubednsService); err != nil {
|
||||
return fmt.Errorf("unable to create/update the kube-dns service: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDNSIP fetches the kubernetes service's ClusterIP and appends a "0" to it in order to get the DNS IP
|
||||
func getDNSIP(client clientset.Interface) (net.IP, error) {
|
||||
k8ssvc, err := client.CoreV1().Services(metav1.NamespaceDefault).Get("kubernetes", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't fetch information about the kubernetes service: %v", err)
|
||||
}
|
||||
|
||||
if len(k8ssvc.Spec.ClusterIP) == 0 {
|
||||
return nil, fmt.Errorf("couldn't fetch a valid clusterIP from the kubernetes service")
|
||||
}
|
||||
|
||||
// Build an IP by taking the kubernetes service's clusterIP and appending a "0" and checking that it's valid
|
||||
dnsIP := net.ParseIP(fmt.Sprintf("%s0", k8ssvc.Spec.ClusterIP))
|
||||
if dnsIP == nil {
|
||||
return nil, fmt.Errorf("could not parse dns ip %q: %v", dnsIP, err)
|
||||
}
|
||||
return dnsIP, nil
|
||||
}
|
||||
121
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/dns_test.go
generated
vendored
Normal file
121
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/dns_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
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 dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestCreateServiceAccount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
createErr error
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
"error-free case",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplication errors should be ignored",
|
||||
apierrors.NewAlreadyExists(api.Resource(""), ""),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unexpected errors should be returned",
|
||||
apierrors.NewUnauthorized(""),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
client := clientsetfake.NewSimpleClientset()
|
||||
if tc.createErr != nil {
|
||||
client.PrependReactor("create", "serviceaccounts", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, tc.createErr
|
||||
})
|
||||
}
|
||||
|
||||
err := CreateServiceAccount(client)
|
||||
if tc.expectErr {
|
||||
if err == nil {
|
||||
t.Errorf("CreateServiceAccounts(%s) wanted err, got nil", tc.name)
|
||||
}
|
||||
continue
|
||||
} else if !tc.expectErr && err != nil {
|
||||
t.Errorf("CreateServiceAccounts(%s) returned unexpected err: %v", tc.name, err)
|
||||
}
|
||||
|
||||
wantResourcesCreated := 1
|
||||
if len(client.Actions()) != wantResourcesCreated {
|
||||
t.Errorf("CreateServiceAccounts(%s) should have made %d actions, but made %d", tc.name, wantResourcesCreated, len(client.Actions()))
|
||||
}
|
||||
|
||||
for _, action := range client.Actions() {
|
||||
if action.GetVerb() != "create" || action.GetResource().Resource != "serviceaccounts" {
|
||||
t.Errorf("CreateServiceAccounts(%s) called [%v %v], but wanted [create serviceaccounts]",
|
||||
tc.name, action.GetVerb(), action.GetResource().Resource)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileManifests(t *testing.T) {
|
||||
var tests = []struct {
|
||||
manifest string
|
||||
data interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
manifest: v170AndAboveKubeDNSDeployment,
|
||||
data: struct{ ImageRepository, Arch, Version, DNSDomain, MasterTaintKey string }{
|
||||
ImageRepository: "foo",
|
||||
Arch: "foo",
|
||||
Version: "foo",
|
||||
DNSDomain: "foo",
|
||||
MasterTaintKey: "foo",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
manifest: KubeDNSService,
|
||||
data: struct{ DNSIP string }{
|
||||
DNSIP: "foo",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, actual := kubeadmutil.ParseTemplate(rt.manifest, rt.data)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed CompileManifests:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
215
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/manifests.go
generated
vendored
Normal file
215
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/manifests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
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 dns
|
||||
|
||||
const (
|
||||
// v170AndAboveKubeDNSDeployment is the kube-dns Deployment manifest for the kube-dns manifest for v1.7+
|
||||
v170AndAboveKubeDNSDeployment = `
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: kube-dns
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
spec:
|
||||
# replicas: not specified here:
|
||||
# 1. In order to make Addon Manager do not reconcile this replicas parameter.
|
||||
# 2. Default is 1.
|
||||
# 3. Will be tuned in real time if DNS horizontal auto-scaling is turned on.
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 10%
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
spec:
|
||||
volumes:
|
||||
- name: kube-dns-config
|
||||
configMap:
|
||||
name: kube-dns
|
||||
optional: true
|
||||
containers:
|
||||
- name: kubedns
|
||||
image: {{ .ImageRepository }}/k8s-dns-kube-dns-{{ .Arch }}:{{ .Version }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
# TODO: Set memory limits when we've profiled the container for large
|
||||
# clusters, then set request = limit to keep this container in
|
||||
# guaranteed class. Currently, this container falls into the
|
||||
# "burstable" category so the kubelet doesn't backoff from restarting it.
|
||||
limits:
|
||||
memory: 170Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 70Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthcheck/kubedns
|
||||
port: 10054
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 60
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readiness
|
||||
port: 8081
|
||||
scheme: HTTP
|
||||
# we poll on pod startup for the Kubernetes master service and
|
||||
# only setup the /readiness HTTP server once that's available.
|
||||
initialDelaySeconds: 3
|
||||
timeoutSeconds: 5
|
||||
args:
|
||||
- --domain={{ .DNSDomain }}.
|
||||
- --dns-port=10053
|
||||
- --config-dir=/kube-dns-config
|
||||
- --v=2
|
||||
env:
|
||||
- name: PROMETHEUS_PORT
|
||||
value: "10055"
|
||||
ports:
|
||||
- containerPort: 10053
|
||||
name: dns-local
|
||||
protocol: UDP
|
||||
- containerPort: 10053
|
||||
name: dns-tcp-local
|
||||
protocol: TCP
|
||||
- containerPort: 10055
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: kube-dns-config
|
||||
mountPath: /kube-dns-config
|
||||
- name: dnsmasq
|
||||
image: {{ .ImageRepository }}/k8s-dns-dnsmasq-nanny-{{ .Arch }}:{{ .Version }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthcheck/dnsmasq
|
||||
port: 10054
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 60
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
args:
|
||||
- -v=2
|
||||
- -logtostderr
|
||||
- -configDir=/etc/k8s/dns/dnsmasq-nanny
|
||||
- -restartDnsmasq=true
|
||||
- --
|
||||
- -k
|
||||
- --cache-size=1000
|
||||
- --log-facility=-
|
||||
- --server=/{{ .DNSDomain }}/127.0.0.1#10053
|
||||
- --server=/in-addr.arpa/127.0.0.1#10053
|
||||
- --server=/ip6.arpa/127.0.0.1#10053
|
||||
ports:
|
||||
- containerPort: 53
|
||||
name: dns
|
||||
protocol: UDP
|
||||
- containerPort: 53
|
||||
name: dns-tcp
|
||||
protocol: TCP
|
||||
# see: https://github.com/kubernetes/kubernetes/issues/29055 for details
|
||||
resources:
|
||||
requests:
|
||||
cpu: 150m
|
||||
memory: 20Mi
|
||||
volumeMounts:
|
||||
- name: kube-dns-config
|
||||
mountPath: /etc/k8s/dns/dnsmasq-nanny
|
||||
- name: sidecar
|
||||
image: {{ .ImageRepository }}/k8s-dns-sidecar-{{ .Arch }}:{{ .Version }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: 10054
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 60
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
args:
|
||||
- --v=2
|
||||
- --logtostderr
|
||||
- --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.{{ .DNSDomain }},5,A
|
||||
- --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.{{ .DNSDomain }},5,A
|
||||
ports:
|
||||
- containerPort: 10054
|
||||
name: metrics
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
memory: 20Mi
|
||||
cpu: 10m
|
||||
dnsPolicy: Default # Don't use cluster DNS.
|
||||
serviceAccountName: kube-dns
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: {{ .MasterTaintKey }}
|
||||
effect: NoSchedule
|
||||
# TODO: Remove this affinity field as soon as we are using manifest lists
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: beta.kubernetes.io/arch
|
||||
operator: In
|
||||
values:
|
||||
- {{ .Arch }}
|
||||
`
|
||||
|
||||
// KubeDNSService is the kube-dns Service manifest
|
||||
KubeDNSService = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-dns
|
||||
kubernetes.io/cluster-service: "true"
|
||||
kubernetes.io/name: "KubeDNS"
|
||||
name: kube-dns
|
||||
namespace: kube-system
|
||||
# Without this resourceVersion value, an update of the Service between versions will yield:
|
||||
# Service "kube-dns" is invalid: metadata.resourceVersion: Invalid value: "": must be specified for an update
|
||||
resourceVersion: "0"
|
||||
spec:
|
||||
clusterIP: {{ .DNSIP }}
|
||||
ports:
|
||||
- name: dns
|
||||
port: 53
|
||||
protocol: UDP
|
||||
targetPort: 53
|
||||
- name: dns-tcp
|
||||
port: 53
|
||||
protocol: TCP
|
||||
targetPort: 53
|
||||
selector:
|
||||
k8s-app: kube-dns
|
||||
`
|
||||
)
|
||||
40
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/versions.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/versions.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2016 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 dns
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
kubeDNSv170AndAboveVersion = "1.14.5"
|
||||
)
|
||||
|
||||
// GetKubeDNSVersion returns the right kube-dns version for a specific k8s version
|
||||
func GetKubeDNSVersion(kubeVersion *version.Version) string {
|
||||
// v1.7.0+ uses 1.14.5, just return that here
|
||||
// In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right versions
|
||||
// Also, the version might be bumped for different k8s releases on the same branch
|
||||
return kubeDNSv170AndAboveVersion
|
||||
}
|
||||
|
||||
// GetKubeDNSManifest returns the right kube-dns YAML manifest for a specific k8s version
|
||||
func GetKubeDNSManifest(kubeVersion *version.Version) string {
|
||||
// v1.7.0+ has only one known YAML manifest spec, just return that here
|
||||
// In the future when the kube-dns version is bumped at HEAD; add conditional logic to return the right manifest
|
||||
return v170AndAboveKubeDNSDeployment
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/versions_test.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns/versions_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright 2016 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 dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
func TestGetKubeDNSVersion(t *testing.T) {
|
||||
var tests = []struct {
|
||||
k8sVersion, expected string
|
||||
}{
|
||||
{
|
||||
k8sVersion: "v1.7.0",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
{
|
||||
k8sVersion: "v1.7.1",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
{
|
||||
k8sVersion: "v1.7.2",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
{
|
||||
k8sVersion: "v1.7.3",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
{
|
||||
k8sVersion: "v1.8.0-alpha.2",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
{
|
||||
k8sVersion: "v1.8.0",
|
||||
expected: "1.14.5",
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
|
||||
k8sVersion, err := version.ParseSemantic(rt.k8sVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't parse kubernetes version %q: %v", rt.k8sVersion, err)
|
||||
}
|
||||
|
||||
actualDNSVersion := GetKubeDNSVersion(k8sVersion)
|
||||
if actualDNSVersion != rt.expected {
|
||||
t.Errorf(
|
||||
"failed GetKubeDNSVersion:\n\texpected: %s\n\t actual: %s",
|
||||
rt.expected,
|
||||
actualDNSVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/BUILD
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["proxy_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"manifests.go",
|
||||
"proxy.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//plugin/pkg/scheduler/algorithm:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
104
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/manifests.go
generated
vendored
Normal file
104
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/manifests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
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 proxy
|
||||
|
||||
const (
|
||||
// KubeProxyConfigMap is the proxy ConfigMap manifest
|
||||
KubeProxyConfigMap = `
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kube-proxy
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app: kube-proxy
|
||||
data:
|
||||
kubeconfig.conf: |
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
server: {{ .MasterEndpoint }}
|
||||
name: default
|
||||
contexts:
|
||||
- context:
|
||||
cluster: default
|
||||
namespace: default
|
||||
user: default
|
||||
name: default
|
||||
current-context: default
|
||||
users:
|
||||
- name: default
|
||||
user:
|
||||
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
`
|
||||
|
||||
// KubeProxyDaemonSet is the proxy DaemonSet manifest
|
||||
KubeProxyDaemonSet = `
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-proxy
|
||||
name: kube-proxy
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: kube-proxy
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: kube-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-proxy
|
||||
image: {{ if .ImageOverride }}{{ .ImageOverride }}{{ else }}{{ .ImageRepository }}/kube-proxy-{{ .Arch }}:{{ .Version }}{{ end }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /usr/local/bin/kube-proxy
|
||||
- --kubeconfig=/var/lib/kube-proxy/kubeconfig.conf
|
||||
{{ .ClusterCIDR }}
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/kube-proxy
|
||||
name: kube-proxy
|
||||
- mountPath: /run/xtables.lock
|
||||
name: xtables-lock
|
||||
readOnly: false
|
||||
hostNetwork: true
|
||||
serviceAccountName: kube-proxy
|
||||
tolerations:
|
||||
- key: {{ .MasterTaintKey }}
|
||||
effect: NoSchedule
|
||||
- key: {{ .CloudTaintKey }}
|
||||
value: "true"
|
||||
effect: NoSchedule
|
||||
volumes:
|
||||
- name: kube-proxy
|
||||
configMap:
|
||||
name: kube-proxy
|
||||
- name: xtables-lock
|
||||
hostPath:
|
||||
path: /run/xtables.lock
|
||||
type: FileOrCreate
|
||||
`
|
||||
)
|
||||
156
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/proxy.go
generated
vendored
Normal file
156
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/proxy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
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 proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
rbac "k8s.io/api/rbac/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kuberuntime "k8s.io/apimachinery/pkg/runtime"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
const (
|
||||
// KubeProxyClusterRoleName sets the name for the kube-proxy ClusterRole
|
||||
// TODO: This k8s-generic, well-known constant should be fetchable from another source, not be in this package
|
||||
KubeProxyClusterRoleName = "system:node-proxier"
|
||||
|
||||
// KubeProxyServiceAccountName describes the name of the ServiceAccount for the kube-proxy addon
|
||||
KubeProxyServiceAccountName = "kube-proxy"
|
||||
)
|
||||
|
||||
// EnsureProxyAddon creates the kube-proxy addons
|
||||
func EnsureProxyAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
|
||||
if err := CreateServiceAccount(client); err != nil {
|
||||
return fmt.Errorf("error when creating kube-proxy service account: %v", err)
|
||||
}
|
||||
|
||||
// Generate Master Enpoint kubeconfig file
|
||||
masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxyConfigMapBytes, err := kubeadmutil.ParseTemplate(KubeProxyConfigMap, struct{ MasterEndpoint string }{
|
||||
// Fetch this value from the kubeconfig file
|
||||
MasterEndpoint: masterEndpoint})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing kube-proxy configmap template: %v", err)
|
||||
}
|
||||
|
||||
proxyDaemonSetBytes, err := kubeadmutil.ParseTemplate(KubeProxyDaemonSet, struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{
|
||||
ImageRepository: cfg.GetControlPlaneImageRepository(),
|
||||
Arch: runtime.GOARCH,
|
||||
Version: kubeadmutil.KubernetesVersionToImageTag(cfg.KubernetesVersion),
|
||||
ImageOverride: cfg.UnifiedControlPlaneImage,
|
||||
ClusterCIDR: getClusterCIDR(cfg.Networking.PodSubnet),
|
||||
MasterTaintKey: kubeadmconstants.LabelNodeRoleMaster,
|
||||
CloudTaintKey: algorithm.TaintExternalCloudProvider,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when parsing kube-proxy daemonset template: %v", err)
|
||||
}
|
||||
|
||||
if err := createKubeProxyAddon(proxyConfigMapBytes, proxyDaemonSetBytes, client); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CreateRBACRules(client); err != nil {
|
||||
return fmt.Errorf("error when creating kube-proxy RBAC rules: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("[addons] Applied essential addon: kube-proxy")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateServiceAccount creates the necessary serviceaccounts that kubeadm uses/might use, if they don't already exist.
|
||||
func CreateServiceAccount(client clientset.Interface) error {
|
||||
|
||||
return apiclient.CreateOrUpdateServiceAccount(client, &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: KubeProxyServiceAccountName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster
|
||||
func CreateRBACRules(client clientset.Interface) error {
|
||||
if err := createClusterRoleBindings(client); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createKubeProxyAddon(configMapBytes, daemonSetbytes []byte, client clientset.Interface) error {
|
||||
kubeproxyConfigMap := &v1.ConfigMap{}
|
||||
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), configMapBytes, kubeproxyConfigMap); err != nil {
|
||||
return fmt.Errorf("unable to decode kube-proxy configmap %v", err)
|
||||
}
|
||||
|
||||
// Create the ConfigMap for kube-proxy or update it in case it already exists
|
||||
if err := apiclient.CreateOrUpdateConfigMap(client, kubeproxyConfigMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeproxyDaemonSet := &extensions.DaemonSet{}
|
||||
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), daemonSetbytes, kubeproxyDaemonSet); err != nil {
|
||||
return fmt.Errorf("unable to decode kube-proxy daemonset %v", err)
|
||||
}
|
||||
|
||||
// Create the DaemonSet for kube-proxy or update it in case it already exists
|
||||
if err := apiclient.CreateOrUpdateDaemonSet(client, kubeproxyDaemonSet); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createClusterRoleBindings(client clientset.Interface) error {
|
||||
return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kubeadm:node-proxier",
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: KubeProxyClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: rbac.ServiceAccountKind,
|
||||
Name: KubeProxyServiceAccountName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func getClusterCIDR(podsubnet string) string {
|
||||
if len(podsubnet) == 0 {
|
||||
return ""
|
||||
}
|
||||
return "- --cluster-cidr=" + podsubnet
|
||||
}
|
||||
140
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go
generated
vendored
Normal file
140
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy/proxy_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
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 proxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestCreateServiceAccount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
createErr error
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
"error-free case",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplication errors should be ignored",
|
||||
apierrors.NewAlreadyExists(api.Resource(""), ""),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unexpected errors should be returned",
|
||||
apierrors.NewUnauthorized(""),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
client := clientsetfake.NewSimpleClientset()
|
||||
if tc.createErr != nil {
|
||||
client.PrependReactor("create", "serviceaccounts", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, tc.createErr
|
||||
})
|
||||
}
|
||||
|
||||
err := CreateServiceAccount(client)
|
||||
if tc.expectErr {
|
||||
if err == nil {
|
||||
t.Errorf("CreateServiceAccounts(%s) wanted err, got nil", tc.name)
|
||||
}
|
||||
continue
|
||||
} else if !tc.expectErr && err != nil {
|
||||
t.Errorf("CreateServiceAccounts(%s) returned unexpected err: %v", tc.name, err)
|
||||
}
|
||||
|
||||
wantResourcesCreated := 1
|
||||
if len(client.Actions()) != wantResourcesCreated {
|
||||
t.Errorf("CreateServiceAccounts(%s) should have made %d actions, but made %d", tc.name, wantResourcesCreated, len(client.Actions()))
|
||||
}
|
||||
|
||||
for _, action := range client.Actions() {
|
||||
if action.GetVerb() != "create" || action.GetResource().Resource != "serviceaccounts" {
|
||||
t.Errorf("CreateServiceAccounts(%s) called [%v %v], but wanted [create serviceaccounts]",
|
||||
tc.name, action.GetVerb(), action.GetResource().Resource)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClusterCIDR(t *testing.T) {
|
||||
emptyClusterCIDR := getClusterCIDR("")
|
||||
if emptyClusterCIDR != "" {
|
||||
t.Errorf("Invalid format: %s", emptyClusterCIDR)
|
||||
}
|
||||
|
||||
clusterCIDR := getClusterCIDR("10.244.0.0/16")
|
||||
if clusterCIDR != "- --cluster-cidr=10.244.0.0/16" {
|
||||
t.Errorf("Invalid format: %s", clusterCIDR)
|
||||
}
|
||||
|
||||
clusterIPv6CIDR := getClusterCIDR("2001:db8::/64")
|
||||
if clusterIPv6CIDR != "- --cluster-cidr=2001:db8::/64" {
|
||||
t.Errorf("Invalid format: %s", clusterIPv6CIDR)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileManifests(t *testing.T) {
|
||||
var tests = []struct {
|
||||
manifest string
|
||||
data interface{}
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
manifest: KubeProxyConfigMap,
|
||||
data: struct{ MasterEndpoint string }{
|
||||
MasterEndpoint: "foo",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
manifest: KubeProxyDaemonSet,
|
||||
data: struct{ ImageRepository, Arch, Version, ImageOverride, ClusterCIDR, MasterTaintKey, CloudTaintKey string }{
|
||||
ImageRepository: "foo",
|
||||
Arch: "foo",
|
||||
Version: "foo",
|
||||
ImageOverride: "foo",
|
||||
ClusterCIDR: "foo",
|
||||
MasterTaintKey: "foo",
|
||||
CloudTaintKey: "foo",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, actual := kubeadmutil.ParseTemplate(rt.manifest, rt.data)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed CompileManifests:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
39
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/BUILD
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["clusterroles_test.go"],
|
||||
library = ":go_default_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["clusterroles.go"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
69
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/clusterroles.go
generated
vendored
Normal file
69
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/clusterroles.go
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 apiconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
// CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster
|
||||
// TODO: This function and phase package is DEPRECATED.
|
||||
// When the v1.9 cycle starts and deletePermissiveNodesBindingWhenUsingNodeAuthorization can be removed, this package will be removed with it.
|
||||
func CreateRBACRules(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
if err := deletePermissiveNodesBindingWhenUsingNodeAuthorization(client, k8sVersion); err != nil {
|
||||
return fmt.Errorf("failed to remove the permissive 'system:nodes' Group Subject in the 'system:node' ClusterRoleBinding: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deletePermissiveNodesBindingWhenUsingNodeAuthorization(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
|
||||
// TODO: When the v1.9 cycle starts (targeting v1.9 at HEAD) and v1.8.0 is the minimum supported version, we can remove this function as the ClusterRoleBinding won't exist
|
||||
// or already have no such permissive subject
|
||||
nodesRoleBinding, err := client.RbacV1beta1().ClusterRoleBindings().Get(kubeadmconstants.NodesClusterRoleBinding, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Nothing to do; the RoleBinding doesn't exist
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
newSubjects := []rbac.Subject{}
|
||||
for _, subject := range nodesRoleBinding.Subjects {
|
||||
// Skip the subject that binds to the system:nodes group
|
||||
if subject.Name == kubeadmconstants.NodesGroup && subject.Kind == "Group" {
|
||||
continue
|
||||
}
|
||||
newSubjects = append(newSubjects, subject)
|
||||
}
|
||||
|
||||
nodesRoleBinding.Subjects = newSubjects
|
||||
|
||||
if _, err := client.RbacV1beta1().ClusterRoleBindings().Update(nodesRoleBinding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
17
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/clusterroles_test.go
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig/clusterroles_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
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 apiconfig
|
||||
50
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["clusterinfo_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["clusterinfo.go"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/apis/rbac/v1beta1:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
105
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
generated
vendored
Normal file
105
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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 clusterinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// BootstrapSignerClusterRoleName sets the name for the ClusterRole that allows access to ConfigMaps in the kube-public ns
|
||||
BootstrapSignerClusterRoleName = "kubeadm:bootstrap-signer-clusterinfo"
|
||||
)
|
||||
|
||||
// CreateBootstrapConfigMapIfNotExists creates the kube-public ConfigMap if it doesn't exist already
|
||||
func CreateBootstrapConfigMapIfNotExists(client clientset.Interface, file string) error {
|
||||
|
||||
fmt.Printf("[bootstraptoken] Creating the %q ConfigMap in the %q namespace\n", bootstrapapi.ConfigMapClusterInfo, metav1.NamespacePublic)
|
||||
|
||||
adminConfig, err := clientcmd.LoadFromFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load admin kubeconfig [%v]", err)
|
||||
}
|
||||
|
||||
adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster
|
||||
// Copy the cluster from admin.conf to the bootstrap kubeconfig, contains the CA cert and the server URL
|
||||
bootstrapConfig := &clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"": adminConfig.Clusters[adminCluster],
|
||||
},
|
||||
}
|
||||
bootstrapBytes, err := clientcmd.Write(*bootstrapConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create or update the ConfigMap in the kube-public namespace
|
||||
return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: bootstrapapi.ConfigMapClusterInfo,
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
Data: map[string]string{
|
||||
bootstrapapi.KubeConfigKey: string(bootstrapBytes),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users
|
||||
func CreateClusterInfoRBACRules(client clientset.Interface) error {
|
||||
err := apiclient.CreateOrUpdateRole(client, &rbac.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: BootstrapSignerClusterRoleName,
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbachelper.NewRule("get").Groups("").Resources("configmaps").Names(bootstrapapi.ConfigMapClusterInfo).RuleOrDie(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: BootstrapSignerClusterRoleName,
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "Role",
|
||||
Name: BootstrapSignerClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: rbac.UserKind,
|
||||
Name: user.Anonymous,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
113
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go
generated
vendored
Normal file
113
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
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 clusterinfo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
var testConfigTempl = template.Must(template.New("test").Parse(`apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: {{.Server}}
|
||||
name: kubernetes
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kubernetes
|
||||
user: kubernetes-admin
|
||||
name: kubernetes-admin@kubernetes
|
||||
current-context: kubernetes-admin@kubernetes
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kubernetes-admin`))
|
||||
|
||||
func TestCreateBootstrapConfigMapIfNotExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
createErr error
|
||||
updateErr error
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
"successful case should have no error",
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"if both create and update errors, return error",
|
||||
apierrors.NewAlreadyExists(api.Resource("configmaps"), "test"),
|
||||
apierrors.NewUnauthorized("go away!"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"unexpected error should be returned",
|
||||
apierrors.NewUnauthorized("go away!"),
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
servers := []struct {
|
||||
Server string
|
||||
}{
|
||||
{Server: "https://10.128.0.6:6443"},
|
||||
{Server: "https://[2001:db8::6]:3446"},
|
||||
}
|
||||
|
||||
for _, server := range servers {
|
||||
file, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tempfile: %v", err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
if err := testConfigTempl.Execute(file, server); err != nil {
|
||||
t.Fatalf("could not write to tempfile: %v", err)
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatalf("could not close tempfile: %v", err)
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
client := clientsetfake.NewSimpleClientset()
|
||||
if tc.createErr != nil {
|
||||
client.PrependReactor("create", "configmaps", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, tc.createErr
|
||||
})
|
||||
}
|
||||
|
||||
err := CreateBootstrapConfigMapIfNotExists(client, file.Name())
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("CreateBootstrapConfigMapIfNotExists(%s) wanted error, got nil", tc.name)
|
||||
} else if !tc.expectErr && err != nil {
|
||||
t.Errorf("CreateBootstrapConfigMapIfNotExists(%s) returned unexpected error: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD
generated
vendored
Normal file
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["token_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"tlsbootstrap.go",
|
||||
"token.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/apis/rbac/v1beta1:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/rbac/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
106
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go
generated
vendored
Normal file
106
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go
generated
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
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 node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
rbac "k8s.io/api/rbac/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodeBootstrapperClusterRoleName defines the name of the auto-bootstrapped ClusterRole for letting someone post a CSR
|
||||
// TODO: This value should be defined in an other, generic authz package instead of here
|
||||
NodeBootstrapperClusterRoleName = "system:node-bootstrapper"
|
||||
// NodeKubeletBootstrap defines the name of the ClusterRoleBinding that lets kubelets post CSRs
|
||||
NodeKubeletBootstrap = "kubeadm:kubelet-bootstrap"
|
||||
|
||||
// CSRAutoApprovalClusterRoleName defines the name of the auto-bootstrapped ClusterRole for making the csrapprover controller auto-approve the CSR
|
||||
// TODO: This value should be defined in an other, generic authz package instead of here
|
||||
CSRAutoApprovalClusterRoleName = "system:certificates.k8s.io:certificatesigningrequests:nodeclient"
|
||||
// NodeAutoApproveBootstrapClusterRoleBinding defines the name of the ClusterRoleBinding that makes the csrapprover approve node CSRs
|
||||
NodeAutoApproveBootstrapClusterRoleBinding = "kubeadm:node-autoapprove-bootstrap"
|
||||
)
|
||||
|
||||
// AllowBootstrapTokensToPostCSRs creates RBAC rules in a way the makes Node Bootstrap Tokens able to post CSRs
|
||||
func AllowBootstrapTokensToPostCSRs(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
|
||||
fmt.Println("[bootstraptoken] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials")
|
||||
|
||||
return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: NodeKubeletBootstrap,
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: NodeBootstrapperClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: rbac.GroupKind,
|
||||
Name: constants.GetNodeBootstrapTokenAuthGroup(k8sVersion),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// AutoApproveNodeBootstrapTokens creates RBAC rules in a way that makes Node Bootstrap Tokens' CSR auto-approved by the csrapprover controller
|
||||
func AutoApproveNodeBootstrapTokens(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
|
||||
fmt.Println("[bootstraptoken] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token")
|
||||
|
||||
// TODO: When the v1.9 cycle starts (targeting v1.9 at HEAD) and v1.8.0 is the minimum supported version, we can remove this function as the ClusterRole will always exist
|
||||
if k8sVersion.LessThan(constants.MinimumCSRAutoApprovalClusterRolesVersion) {
|
||||
|
||||
err := apiclient.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: CSRAutoApprovalClusterRoleName,
|
||||
},
|
||||
Rules: []rbac.PolicyRule{
|
||||
rbachelper.NewRule("create").Groups("certificates.k8s.io").Resources("certificatesigningrequests/nodeclient").RuleOrDie(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Always create this kubeadm-specific binding though
|
||||
return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: NodeAutoApproveBootstrapClusterRoleBinding,
|
||||
},
|
||||
RoleRef: rbac.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "ClusterRole",
|
||||
Name: CSRAutoApprovalClusterRoleName,
|
||||
},
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: constants.GetNodeBootstrapTokenAuthGroup(k8sVersion),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
112
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/token.go
generated
vendored
Normal file
112
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/token.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
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 node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
const tokenCreateRetries = 5
|
||||
|
||||
// TODO(mattmoyer): Move CreateNewToken, UpdateOrCreateToken and encodeTokenSecretData out of this package to client-go for a generic abstraction and client for a Bootstrap Token
|
||||
|
||||
// CreateNewToken tries to create a token and fails if one with the same ID already exists
|
||||
func CreateNewToken(client clientset.Interface, token string, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error {
|
||||
return UpdateOrCreateToken(client, token, true, tokenDuration, usages, extraGroups, description)
|
||||
}
|
||||
|
||||
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does not already exist.
|
||||
func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists bool, tokenDuration time.Duration, usages []string, extraGroups []string, description string) error {
|
||||
tokenID, tokenSecret, err := tokenutil.ParseToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenID)
|
||||
var lastErr error
|
||||
for i := 0; i < tokenCreateRetries; i++ {
|
||||
secret, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
if failIfExists {
|
||||
return fmt.Errorf("a token with id %q already exists", tokenID)
|
||||
}
|
||||
// Secret with this ID already exists, update it:
|
||||
secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description)
|
||||
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
|
||||
return nil
|
||||
}
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
// Secret does not already exist:
|
||||
if apierrors.IsNotFound(err) {
|
||||
secret = &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
||||
Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description),
|
||||
}
|
||||
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
||||
return nil
|
||||
}
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"unable to create bootstrap token after %d attempts [%v]",
|
||||
tokenCreateRetries,
|
||||
lastErr,
|
||||
)
|
||||
}
|
||||
|
||||
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
|
||||
func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) map[string][]byte {
|
||||
data := map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
|
||||
}
|
||||
|
||||
if len(extraGroups) > 0 {
|
||||
data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(strings.Join(extraGroups, ","))
|
||||
}
|
||||
|
||||
if duration > 0 {
|
||||
// Get the current time, add the specified duration, and format it accordingly
|
||||
durationString := time.Now().Add(duration).Format(time.RFC3339)
|
||||
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(durationString)
|
||||
}
|
||||
if len(description) > 0 {
|
||||
data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description)
|
||||
}
|
||||
for _, usage := range usages {
|
||||
// TODO: Validate the usage string here before
|
||||
data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true")
|
||||
}
|
||||
return data
|
||||
}
|
||||
59
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node/token_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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 node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
|
||||
func TestEncodeTokenSecretData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token *kubeadmapi.TokenDiscovery
|
||||
t time.Duration
|
||||
}{
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}}, // should use default
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "")
|
||||
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.ID,
|
||||
actual["token-id"],
|
||||
)
|
||||
}
|
||||
if !bytes.Equal(actual["token-secret"], []byte(rt.token.Secret)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.Secret,
|
||||
actual["token-secret"],
|
||||
)
|
||||
}
|
||||
if rt.t > 0 {
|
||||
if actual["expiration"] == nil {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData, duration was not added to time",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/BUILD
generated
vendored
Normal file
52
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["certs_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
"//cmd/kubeadm/test/certs:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"certs.go",
|
||||
"doc.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//pkg/registry/core/service/ipallocator:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
552
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/certs.go
generated
vendored
Normal file
552
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/certs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
/*
|
||||
Copyright 2016 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 certs
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
"k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
||||
// If the PKI assets already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned.
|
||||
func CreatePKIAssets(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
certActions := []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreateCACertAndKeyfiles,
|
||||
CreateAPIServerCertAndKeyFiles,
|
||||
CreateAPIServerKubeletClientCertAndKeyFiles,
|
||||
CreateServiceAccountKeyAndPublicKeyFiles,
|
||||
CreateFrontProxyCACertAndKeyFiles,
|
||||
CreateFrontProxyClientCertAndKeyFiles,
|
||||
}
|
||||
|
||||
for _, action := range certActions {
|
||||
err := action(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Valid certificates and keys now exist in %q\n", cfg.CertificatesDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCACertAndKeyfiles create a new self signed CA certificate and key files.
|
||||
// If the CA certificate and key files already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateCACertAndKeyfiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
caCert, caKey, err := NewCACertAndKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeCertificateAuthorithyFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.CACertAndKeyBaseName,
|
||||
caCert,
|
||||
caKey,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateAPIServerCertAndKeyFiles create a new certificate and key files for the apiserver.
|
||||
// If the apiserver certificate and key files already exists in the target folder, they are used only if evaluated equal; otherwise an error is returned.
|
||||
// It assumes the cluster CA certificate and key files should exists into the CertificatesDir
|
||||
func CreateAPIServerCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
caCert, caKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiCert, apiKey, err := NewAPIServerCertAndKey(cfg, caCert, caKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeCertificateFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.APIServerCertAndKeyBaseName,
|
||||
caCert,
|
||||
apiCert,
|
||||
apiKey,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateAPIServerKubeletClientCertAndKeyFiles create a new CA certificate for kubelets calling apiserver
|
||||
// If the apiserver-kubelet-client certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned.
|
||||
// It assumes the cluster CA certificate and key files should exists into the CertificatesDir
|
||||
func CreateAPIServerKubeletClientCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
caCert, caKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiClientCert, apiClientKey, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeCertificateFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
|
||||
caCert,
|
||||
apiClientCert,
|
||||
apiClientKey,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateServiceAccountKeyAndPublicKeyFiles create a new public/private key files for signing service account users.
|
||||
// If the sa public/private key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned.
|
||||
func CreateServiceAccountKeyAndPublicKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
saSigningKey, err := NewServiceAccountSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeKeyFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.ServiceAccountKeyBaseName,
|
||||
saSigningKey,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateFrontProxyCACertAndKeyFiles create a self signed front proxy CA certificate and key files.
|
||||
// Front proxy CA and client certs are used to secure a front proxy authenticator which is used to assert identity
|
||||
// without the client cert; This is a separte CA, so that front proxy identities cannot hit the API and normal client certs cannot be used
|
||||
// as front proxies.
|
||||
// If the front proxy CA certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned.
|
||||
func CreateFrontProxyCACertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeCertificateAuthorithyFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
frontProxyCACert,
|
||||
frontProxyCAKey,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateFrontProxyClientCertAndKeyFiles create a new certificate for proxy server client.
|
||||
// If the front-proxy-client certificate and key files already exists in the target folder, they are used only if evaluated equals; otherwise an error is returned.
|
||||
// It assumes the front proxy CAA certificate and key files should exists into the CertificatesDir
|
||||
func CreateFrontProxyClientCertAndKeyFiles(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
frontProxyCACert, frontProxyCAKey, err := loadCertificateAuthorithy(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frontProxyClientCert, frontProxyClientKey, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeCertificateFilesIfNotExist(
|
||||
cfg.CertificatesDir,
|
||||
kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
|
||||
frontProxyCACert,
|
||||
frontProxyClientCert,
|
||||
frontProxyClientKey,
|
||||
)
|
||||
}
|
||||
|
||||
// NewCACertAndKey will generate a self signed CA.
|
||||
func NewCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
caCert, caKey, err := pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err)
|
||||
}
|
||||
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
// NewAPIServerCertAndKey generate CA certificate for apiserver, signed by the given CA.
|
||||
func NewAPIServerCertAndKey(cfg *kubeadmapi.MasterConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
altNames, err := getAltNames(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while composing altnames for API server: %v", err)
|
||||
}
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.APIServerCertCommonName,
|
||||
AltNames: *altNames,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
apiCert, apiKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating API server key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return apiCert, apiKey, nil
|
||||
}
|
||||
|
||||
// NewAPIServerKubeletClientCertAndKey generate CA certificate for the apiservers to connect to the kubelets securely, signed by the given CA.
|
||||
func NewAPIServerKubeletClientCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.APIServerKubeletClientCertCommonName,
|
||||
Organization: []string{kubeadmconstants.MastersGroup},
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
apiClientCert, apiClientKey, err := pkiutil.NewCertAndKey(caCert, caKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating API server kubelet client key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return apiClientCert, apiClientKey, nil
|
||||
}
|
||||
|
||||
// NewServiceAccountSigningKey generate public/private key pairs for signing service account tokens.
|
||||
func NewServiceAccountSigningKey() (*rsa.PrivateKey, error) {
|
||||
|
||||
// The key does NOT exist, let's generate it now
|
||||
saSigningKey, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure while creating service account token signing key: %v", err)
|
||||
}
|
||||
|
||||
return saSigningKey, nil
|
||||
}
|
||||
|
||||
// NewFrontProxyCACertAndKey generate a self signed front proxy CA.
|
||||
func NewFrontProxyCACertAndKey() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
frontProxyCACert, frontProxyCAKey, err := pkiutil.NewCertificateAuthority()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while generating front-proxy CA certificate and key: %v", err)
|
||||
}
|
||||
|
||||
return frontProxyCACert, frontProxyCAKey, nil
|
||||
}
|
||||
|
||||
// NewFrontProxyClientCertAndKey generate CA certificate for proxy server client, signed by the given front proxy CA.
|
||||
func NewFrontProxyClientCertAndKey(frontProxyCACert *x509.Certificate, frontProxyCAKey *rsa.PrivateKey) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: kubeadmconstants.FrontProxyClientCertCommonName,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
frontProxyClientCert, frontProxyClientKey, err := pkiutil.NewCertAndKey(frontProxyCACert, frontProxyCAKey, config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure while creating front-proxy client key and certificate: %v", err)
|
||||
}
|
||||
|
||||
return frontProxyClientCert, frontProxyClientKey, nil
|
||||
}
|
||||
|
||||
// loadCertificateAuthorithy loads certificate authorithy
|
||||
func loadCertificateAuthorithy(pkiDir string, baseName string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
// Checks if certificate authorithy exists in the PKI directory
|
||||
if !pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
return nil, nil, fmt.Errorf("couldn't load %s certificate authorithy from %s", baseName, pkiDir)
|
||||
}
|
||||
|
||||
// Try to load certificate authorithy .crt and .key from the PKI directory
|
||||
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failure loading %s certificate authorithy: %v", baseName, err)
|
||||
}
|
||||
|
||||
// Make sure the loaded CA cert actually is a CA
|
||||
if !caCert.IsCA {
|
||||
return nil, nil, fmt.Errorf("%s certificate is not a certificate authorithy", baseName)
|
||||
}
|
||||
|
||||
return caCert, caKey, nil
|
||||
}
|
||||
|
||||
// writeCertificateAuthorithyFilesIfNotExist write a new certificate Authorithy to the given path.
|
||||
// If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the
|
||||
// existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
|
||||
// otherwise this function returns an error.
|
||||
func writeCertificateAuthorithyFilesIfNotExist(pkiDir string, baseName string, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
|
||||
|
||||
// If cert or key exists, we should try to load them
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
|
||||
// Try to load .crt and .key from the PKI directory
|
||||
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading %s certificate: %v", baseName, err)
|
||||
}
|
||||
|
||||
// Check if the existing cert is a CA
|
||||
if !caCert.IsCA {
|
||||
return fmt.Errorf("certificate %s is not a CA", baseName)
|
||||
}
|
||||
|
||||
// kubeadm doesn't validate the existing certificate Authorithy more than this;
|
||||
// Basically, if we find a certificate file with the same path; and it is a CA
|
||||
// kubeadm thinks those files are equal and doesn't bother writing a new file
|
||||
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName)
|
||||
} else {
|
||||
|
||||
// Write .crt and .key files to disk
|
||||
if err := pkiutil.WriteCertAndKey(pkiDir, baseName, caCert, caKey); err != nil {
|
||||
return fmt.Errorf("failure while saving %s certificate and key: %v", baseName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeCertificateFilesIfNotExist write a new certificate to the given path.
|
||||
// If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the
|
||||
// existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
|
||||
// otherwise this function returns an error.
|
||||
func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert *x509.Certificate, cert *x509.Certificate, key *rsa.PrivateKey) error {
|
||||
|
||||
// Checks if the signed certificate exists in the PKI directory
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
// Try to load signed certificate .crt and .key from the PKI directory
|
||||
signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading %s certificate: %v", baseName, err)
|
||||
}
|
||||
|
||||
// Check if the existing cert is signed by the given CA
|
||||
if err := signedCert.CheckSignatureFrom(signingCert); err != nil {
|
||||
return fmt.Errorf("certificate %s is not signed by corresponding CA", baseName)
|
||||
}
|
||||
|
||||
// kubeadm doesn't validate the existing certificate more than this;
|
||||
// Basically, if we find a certificate file with the same path; and it is signed by
|
||||
// the expected certificate authorithy, kubeadm thinks those files are equal and
|
||||
// doesn't bother writing a new file
|
||||
fmt.Printf("[certificates] Using the existing %s certificate and key.\n", baseName)
|
||||
} else {
|
||||
|
||||
// Write .crt and .key files to disk
|
||||
if err := pkiutil.WriteCertAndKey(pkiDir, baseName, cert, key); err != nil {
|
||||
return fmt.Errorf("failure while saving %s certificate and key: %v", baseName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[certificates] Generated %s certificate and key.\n", baseName)
|
||||
if pkiutil.HasServerAuth(cert) {
|
||||
fmt.Printf("[certificates] %s serving cert is signed for DNS names %v and IPs %v\n", baseName, cert.DNSNames, cert.IPAddresses)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeKeyFilesIfNotExist write a new key to the given path.
|
||||
// If there already is a key file at the given path; kubeadm tries to load it and check if the values in the
|
||||
// existing and the expected key equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
|
||||
// otherwise this function returns an error.
|
||||
func writeKeyFilesIfNotExist(pkiDir string, baseName string, key *rsa.PrivateKey) error {
|
||||
|
||||
// Checks if the key exists in the PKI directory
|
||||
if pkiutil.CertOrKeyExist(pkiDir, baseName) {
|
||||
|
||||
// Try to load .key from the PKI directory
|
||||
_, err := pkiutil.TryLoadKeyFromDisk(pkiDir, baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s key existed but it could not be loaded properly: %v", baseName, err)
|
||||
}
|
||||
|
||||
// kubeadm doesn't validate the existing certificate key more than this;
|
||||
// Basically, if we find a key file with the same path kubeadm thinks those files
|
||||
// are equal and doesn't bother writing a new file
|
||||
fmt.Printf("[certificates] Using the existing %s key.\n", baseName)
|
||||
} else {
|
||||
|
||||
// Write .key and .pub files to disk
|
||||
if err := pkiutil.WriteKey(pkiDir, baseName, key); err != nil {
|
||||
return fmt.Errorf("failure while saving %s key: %v", baseName, err)
|
||||
}
|
||||
|
||||
if err := pkiutil.WritePublicKey(pkiDir, baseName, &key.PublicKey); err != nil {
|
||||
return fmt.Errorf("failure while saving %s public key: %v", baseName, err)
|
||||
}
|
||||
fmt.Printf("[certificates] Generated %s key and public key.\n", baseName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type certKeyLocation struct {
|
||||
pkiDir string
|
||||
caBaseName string
|
||||
baseName string
|
||||
uxName string
|
||||
}
|
||||
|
||||
// UsingExternalCA determines whether the user is relying on an external CA. We currently implicitly determine this is the case when the CA Cert
|
||||
// is present but the CA Key is not. This allows us to, e.g., skip generating certs or not start the csr signing controller.
|
||||
func UsingExternalCA(cfg *kubeadmapi.MasterConfiguration) (bool, error) {
|
||||
|
||||
if err := validateCACert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName, "", "CA"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
caKeyPath := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName)
|
||||
if _, err := os.Stat(caKeyPath); !os.IsNotExist(err) {
|
||||
return false, fmt.Errorf("ca.key exists")
|
||||
}
|
||||
|
||||
if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName, kubeadmconstants.APIServerCertAndKeyBaseName, "API server"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName, kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, "API server kubelet client"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := validatePrivatePublicKey(certKeyLocation{cfg.CertificatesDir, "", kubeadmconstants.ServiceAccountKeyBaseName, "service account"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := validateCACertAndKey(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName, "", "front-proxy CA"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := validateSignedCert(certKeyLocation{cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertAndKeyBaseName, kubeadmconstants.FrontProxyClientCertAndKeyBaseName, "front-proxy client"}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// validateCACert tries to load a x509 certificate from pkiDir and validates that it is a CA
|
||||
func validateCACert(l certKeyLocation) error {
|
||||
// Check CA Cert
|
||||
caCert, err := pkiutil.TryLoadCertFromDisk(l.pkiDir, l.caBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading certificate for %s: %v", l.uxName, err)
|
||||
}
|
||||
|
||||
// Check if cert is a CA
|
||||
if !caCert.IsCA {
|
||||
return fmt.Errorf("certificate %s is not a CA", l.uxName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateCACertAndKey tries to load a x509 certificate and private key from pkiDir,
|
||||
// and validates that the cert is a CA
|
||||
func validateCACertAndKey(l certKeyLocation) error {
|
||||
if err := validateCACert(l); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := pkiutil.TryLoadKeyFromDisk(l.pkiDir, l.caBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading key for %s: %v", l.uxName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSignedCert tries to load a x509 certificate and private key from pkiDir and validates
|
||||
// that the cert is signed by a given CA
|
||||
func validateSignedCert(l certKeyLocation) error {
|
||||
// Try to load CA
|
||||
caCert, err := pkiutil.TryLoadCertFromDisk(l.pkiDir, l.caBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading certificate authorithy for %s: %v", l.uxName, err)
|
||||
}
|
||||
|
||||
// Try to load key and signed certificate
|
||||
signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(l.pkiDir, l.baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading certificate for %s: %v", l.uxName, err)
|
||||
}
|
||||
|
||||
// Check if the cert is signed by the CA
|
||||
if err := signedCert.CheckSignatureFrom(caCert); err != nil {
|
||||
return fmt.Errorf("certificate %s is not signed by corresponding CA", l.uxName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePrivatePublicKey tries to load a private key from pkiDir
|
||||
func validatePrivatePublicKey(l certKeyLocation) error {
|
||||
// Try to load key
|
||||
_, _, err := pkiutil.TryLoadPrivatePublicKeyFromDisk(l.pkiDir, l.baseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure loading key for %s: %v", l.uxName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAltNames builds an AltNames object for to be used when generating apiserver certificate
|
||||
func getAltNames(cfg *kubeadmapi.MasterConfiguration) (*certutil.AltNames, error) {
|
||||
|
||||
// advertise address
|
||||
advertiseAddress := net.ParseIP(cfg.API.AdvertiseAddress)
|
||||
if advertiseAddress == nil {
|
||||
return nil, fmt.Errorf("error parsing API AdvertiseAddress %v: is not a valid textual representation of an IP address", cfg.API.AdvertiseAddress)
|
||||
}
|
||||
|
||||
// internal IP address for the API server
|
||||
_, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing CIDR %q: %v", cfg.Networking.ServiceSubnet, err)
|
||||
}
|
||||
|
||||
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get first IP address from the given CIDR (%s): %v", svcSubnet.String(), err)
|
||||
}
|
||||
|
||||
// create AltNames with defaults DNSNames/IPs
|
||||
altNames := &certutil.AltNames{
|
||||
DNSNames: []string{
|
||||
cfg.NodeName,
|
||||
"kubernetes",
|
||||
"kubernetes.default",
|
||||
"kubernetes.default.svc",
|
||||
fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain),
|
||||
},
|
||||
IPs: []net.IP{
|
||||
internalAPIServerVirtualIP,
|
||||
advertiseAddress,
|
||||
},
|
||||
}
|
||||
|
||||
// adds additional SAN
|
||||
for _, altname := range cfg.APIServerCertSANs {
|
||||
if ip := net.ParseIP(altname); ip != nil {
|
||||
altNames.IPs = append(altNames.IPs, ip)
|
||||
} else if len(validation.IsDNS1123Subdomain(altname)) == 0 {
|
||||
altNames.DNSNames = append(altNames.DNSNames, altname)
|
||||
}
|
||||
}
|
||||
|
||||
return altNames, nil
|
||||
}
|
||||
606
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/certs_test.go
generated
vendored
Normal file
606
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/certs_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
/*
|
||||
Copyright 2016 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 certs
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
|
||||
)
|
||||
|
||||
func TestWriteCertificateAuthorithyFilesIfNotExist(t *testing.T) {
|
||||
|
||||
setupCert, setupKey, _ := NewCACertAndKey()
|
||||
caCert, caKey, _ := NewCACertAndKey()
|
||||
|
||||
var tests = []struct {
|
||||
setupFunc func(pkiDir string) error
|
||||
expectedError bool
|
||||
expectedCa *x509.Certificate
|
||||
}{
|
||||
{ // ca cert does not exists > ca written
|
||||
expectedCa: caCert,
|
||||
},
|
||||
{ // ca cert exists, is ca > existing ca used
|
||||
setupFunc: func(pkiDir string) error {
|
||||
return writeCertificateAuthorithyFilesIfNotExist(pkiDir, "dummy", setupCert, setupKey)
|
||||
},
|
||||
expectedCa: setupCert,
|
||||
},
|
||||
{ // some file exists, but it is not a valid ca cert > err
|
||||
setupFunc: func(pkiDir string) error {
|
||||
testutil.SetupEmptyFiles(t, pkiDir, "dummy.crt")
|
||||
return nil
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{ // cert exists, but it is not a ca > err
|
||||
setupFunc: func(pkiDir string) error {
|
||||
cert, key, _ := NewFrontProxyClientCertAndKey(setupCert, setupKey)
|
||||
return writeCertificateFilesIfNotExist(pkiDir, "dummy", setupCert, cert, key)
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// executes setup func (if necessary)
|
||||
if test.setupFunc != nil {
|
||||
if err := test.setupFunc(tmpdir); err != nil {
|
||||
t.Errorf("error executing setupFunc: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// executes create func
|
||||
err := writeCertificateAuthorithyFilesIfNotExist(tmpdir, "dummy", caCert, caKey)
|
||||
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("error writeCertificateAuthorithyFilesIfNotExist failed when not expected to fail: %v", err)
|
||||
continue
|
||||
} else if test.expectedError && err == nil {
|
||||
t.Error("error writeCertificateAuthorithyFilesIfNotExist didn't failed when expected")
|
||||
continue
|
||||
} else if test.expectedError {
|
||||
continue
|
||||
}
|
||||
|
||||
// asserts expected files are there
|
||||
testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.crt")
|
||||
|
||||
// check created cert
|
||||
resultingCaCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpdir, "dummy")
|
||||
if err != nil {
|
||||
t.Errorf("failure reading created cert: %v", err)
|
||||
continue
|
||||
}
|
||||
if !resultingCaCert.Equal(test.expectedCa) {
|
||||
t.Error("created ca cert does not match expected ca cert")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCertificateFilesIfNotExist(t *testing.T) {
|
||||
|
||||
caCert, caKey, _ := NewFrontProxyCACertAndKey()
|
||||
setupCert, setupKey, _ := NewFrontProxyClientCertAndKey(caCert, caKey)
|
||||
cert, key, _ := NewFrontProxyClientCertAndKey(caCert, caKey)
|
||||
|
||||
var tests = []struct {
|
||||
setupFunc func(pkiDir string) error
|
||||
expectedError bool
|
||||
expectedCert *x509.Certificate
|
||||
}{
|
||||
{ // cert does not exists > cert written
|
||||
expectedCert: cert,
|
||||
},
|
||||
{ // cert exists, is signed by the same ca > existing cert used
|
||||
setupFunc: func(pkiDir string) error {
|
||||
return writeCertificateFilesIfNotExist(pkiDir, "dummy", caCert, setupCert, setupKey)
|
||||
},
|
||||
expectedCert: setupCert,
|
||||
},
|
||||
{ // some file exists, but it is not a valid cert > err
|
||||
setupFunc: func(pkiDir string) error {
|
||||
testutil.SetupEmptyFiles(t, pkiDir, "dummy.crt")
|
||||
return nil
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{ // cert exists, is signed by another ca > err
|
||||
setupFunc: func(pkiDir string) error {
|
||||
anotherCaCert, anotherCaKey, _ := NewFrontProxyCACertAndKey()
|
||||
anotherCert, anotherKey, _ := NewFrontProxyClientCertAndKey(anotherCaCert, anotherCaKey)
|
||||
|
||||
return writeCertificateFilesIfNotExist(pkiDir, "dummy", anotherCaCert, anotherCert, anotherKey)
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// executes setup func (if necessary)
|
||||
if test.setupFunc != nil {
|
||||
if err := test.setupFunc(tmpdir); err != nil {
|
||||
t.Errorf("error executing setupFunc: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// executes create func
|
||||
err := writeCertificateFilesIfNotExist(tmpdir, "dummy", caCert, cert, key)
|
||||
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("error writeCertificateFilesIfNotExist failed when not expected to fail: %v", err)
|
||||
continue
|
||||
} else if test.expectedError && err == nil {
|
||||
t.Error("error writeCertificateFilesIfNotExist didn't failed when expected")
|
||||
continue
|
||||
} else if test.expectedError {
|
||||
continue
|
||||
}
|
||||
|
||||
// asserts expected files are there
|
||||
testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.crt")
|
||||
|
||||
// check created cert
|
||||
resultingCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpdir, "dummy")
|
||||
if err != nil {
|
||||
t.Errorf("failure reading created cert: %v", err)
|
||||
continue
|
||||
}
|
||||
if !resultingCert.Equal(test.expectedCert) {
|
||||
t.Error("created cert does not match expected cert")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteKeyFilesIfNotExist(t *testing.T) {
|
||||
|
||||
setupKey, _ := NewServiceAccountSigningKey()
|
||||
key, _ := NewServiceAccountSigningKey()
|
||||
|
||||
var tests = []struct {
|
||||
setupFunc func(pkiDir string) error
|
||||
expectedError bool
|
||||
expectedKey *rsa.PrivateKey
|
||||
}{
|
||||
{ // key does not exists > key written
|
||||
expectedKey: key,
|
||||
},
|
||||
{ // key exists > existing key used
|
||||
setupFunc: func(pkiDir string) error {
|
||||
return writeKeyFilesIfNotExist(pkiDir, "dummy", setupKey)
|
||||
},
|
||||
expectedKey: setupKey,
|
||||
},
|
||||
{ // some file exists, but it is not a valid key > err
|
||||
setupFunc: func(pkiDir string) error {
|
||||
testutil.SetupEmptyFiles(t, pkiDir, "dummy.key")
|
||||
return nil
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// executes setup func (if necessary)
|
||||
if test.setupFunc != nil {
|
||||
if err := test.setupFunc(tmpdir); err != nil {
|
||||
t.Errorf("error executing setupFunc: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// executes create func
|
||||
err := writeKeyFilesIfNotExist(tmpdir, "dummy", key)
|
||||
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("error writeKeyFilesIfNotExist failed when not expected to fail: %v", err)
|
||||
continue
|
||||
} else if test.expectedError && err == nil {
|
||||
t.Error("error writeKeyFilesIfNotExist didn't failed when expected")
|
||||
continue
|
||||
} else if test.expectedError {
|
||||
continue
|
||||
}
|
||||
|
||||
// asserts expected files are there
|
||||
testutil.AssertFileExists(t, tmpdir, "dummy.key", "dummy.pub")
|
||||
|
||||
// check created key
|
||||
resultingKey, err := pkiutil.TryLoadKeyFromDisk(tmpdir, "dummy")
|
||||
if err != nil {
|
||||
t.Errorf("failure reading created key: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
//TODO: check if there is a better method to compare keys
|
||||
if resultingKey.D == key.D {
|
||||
t.Error("created key does not match expected key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAltNames(t *testing.T) {
|
||||
hostname := "valid-hostname"
|
||||
advertiseIP := "1.2.3.4"
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: advertiseIP},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: hostname,
|
||||
}
|
||||
|
||||
altNames, err := getAltNames(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed calling getAltNames: %v", err)
|
||||
}
|
||||
|
||||
expectedDNSNames := []string{hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"}
|
||||
for _, DNSName := range expectedDNSNames {
|
||||
found := false
|
||||
for _, val := range altNames.DNSNames {
|
||||
if val == DNSName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("altNames does not contain DNSName %s", DNSName)
|
||||
}
|
||||
}
|
||||
|
||||
expectedIPAddresses := []string{"10.96.0.1", advertiseIP}
|
||||
for _, IPAddress := range expectedIPAddresses {
|
||||
found := false
|
||||
for _, val := range altNames.IPs {
|
||||
if val.Equal(net.ParseIP(IPAddress)) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("altNames does not contain IPAddress %s", IPAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCACertAndKey(t *testing.T) {
|
||||
caCert, _, err := NewCACertAndKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed call NewCACertAndKey: %v", err)
|
||||
}
|
||||
|
||||
certstestutil.AssertCertificateIsCa(t, caCert)
|
||||
}
|
||||
|
||||
func TestNewAPIServerCertAndKey(t *testing.T) {
|
||||
hostname := "valid-hostname"
|
||||
|
||||
advertiseAddresses := []string{"1.2.3.4", "1:2:3::4"}
|
||||
for _, addr := range advertiseAddresses {
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: addr},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
}
|
||||
caCert, caKey, err := NewCACertAndKey()
|
||||
|
||||
apiServerCert, _, err := NewAPIServerCertAndKey(cfg, caCert, caKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
certstestutil.AssertCertificateIsSignedByCa(t, apiServerCert, caCert)
|
||||
certstestutil.AssertCertificateHasServerAuthUsage(t, apiServerCert)
|
||||
certstestutil.AssertCertificateHasDNSNames(t, apiServerCert, hostname, "kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local")
|
||||
certstestutil.AssertCertificateHasIPAddresses(t, apiServerCert, net.ParseIP("10.96.0.1"), net.ParseIP(addr))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAPIServerKubeletClientCertAndKey(t *testing.T) {
|
||||
caCert, caKey, err := NewCACertAndKey()
|
||||
|
||||
apiClientCert, _, err := NewAPIServerKubeletClientCertAndKey(caCert, caKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
certstestutil.AssertCertificateIsSignedByCa(t, apiClientCert, caCert)
|
||||
certstestutil.AssertCertificateHasClientAuthUsage(t, apiClientCert)
|
||||
certstestutil.AssertCertificateHasOrganizations(t, apiClientCert, kubeadmconstants.MastersGroup)
|
||||
}
|
||||
|
||||
func TestNewNewServiceAccountSigningKey(t *testing.T) {
|
||||
|
||||
key, err := NewServiceAccountSigningKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of key: %v", err)
|
||||
}
|
||||
|
||||
if key.N.BitLen() < 2048 {
|
||||
t.Error("Service account signing key has less than 2048 bits size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFrontProxyCACertAndKey(t *testing.T) {
|
||||
frontProxyCACert, _, err := NewFrontProxyCACertAndKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
certstestutil.AssertCertificateIsCa(t, frontProxyCACert)
|
||||
}
|
||||
|
||||
func TestNewFrontProxyClientCertAndKey(t *testing.T) {
|
||||
frontProxyCACert, frontProxyCAKey, err := NewFrontProxyCACertAndKey()
|
||||
|
||||
frontProxyClientCert, _, err := NewFrontProxyClientCertAndKey(frontProxyCACert, frontProxyCAKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed creation of cert and key: %v", err)
|
||||
}
|
||||
|
||||
certstestutil.AssertCertificateIsSignedByCa(t, frontProxyClientCert, frontProxyCACert)
|
||||
certstestutil.AssertCertificateHasClientAuthUsage(t, frontProxyClientCert)
|
||||
}
|
||||
|
||||
func TestUsingExternalCA(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
setupFuncs []func(cfg *kubeadmapi.MasterConfiguration) error
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreatePKIAssets,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreatePKIAssets,
|
||||
deleteCAKey,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
dir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
CertificatesDir: dir,
|
||||
}
|
||||
|
||||
for _, f := range test.setupFuncs {
|
||||
if err := f(cfg); err != nil {
|
||||
t.Errorf("error executing setup function: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if val, _ := UsingExternalCA(cfg); val != test.expected {
|
||||
t.Errorf("UsingExternalCA did not match expected: %v", test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMethods(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupFuncs []func(cfg *kubeadmapi.MasterConfiguration) error
|
||||
validateFunc func(l certKeyLocation) error
|
||||
loc certKeyLocation
|
||||
expectedSuccess bool
|
||||
}{
|
||||
{
|
||||
name: "validateCACert",
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreateCACertAndKeyfiles,
|
||||
},
|
||||
validateFunc: validateCACert,
|
||||
loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
|
||||
expectedSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "validateCACertAndKey (files present)",
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreateCACertAndKeyfiles,
|
||||
},
|
||||
validateFunc: validateCACertAndKey,
|
||||
loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
|
||||
expectedSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "validateCACertAndKey (key missing)",
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreatePKIAssets,
|
||||
deleteCAKey,
|
||||
},
|
||||
validateFunc: validateCACertAndKey,
|
||||
loc: certKeyLocation{caBaseName: "ca", baseName: "", uxName: "CA"},
|
||||
expectedSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "validateSignedCert",
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreateCACertAndKeyfiles,
|
||||
CreateAPIServerCertAndKeyFiles,
|
||||
},
|
||||
validateFunc: validateSignedCert,
|
||||
loc: certKeyLocation{caBaseName: "ca", baseName: "apiserver", uxName: "apiserver"},
|
||||
expectedSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "validatePrivatePublicKey",
|
||||
setupFuncs: []func(cfg *kubeadmapi.MasterConfiguration) error{
|
||||
CreateServiceAccountKeyAndPublicKeyFiles,
|
||||
},
|
||||
validateFunc: validatePrivatePublicKey,
|
||||
loc: certKeyLocation{baseName: "sa", uxName: "service account"},
|
||||
expectedSuccess: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
dir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
test.loc.pkiDir = dir
|
||||
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
CertificatesDir: dir,
|
||||
}
|
||||
|
||||
fmt.Println("Testing", test.name)
|
||||
|
||||
for _, f := range test.setupFuncs {
|
||||
if err := f(cfg); err != nil {
|
||||
t.Errorf("error executing setup function: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := test.validateFunc(test.loc)
|
||||
if test.expectedSuccess && err != nil {
|
||||
t.Errorf("expected success, error executing validateFunc: %v, %v", test.name, err)
|
||||
} else if !test.expectedSuccess && err == nil {
|
||||
t.Errorf("expected failure, no error executing validateFunc: %v", test.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteCAKey(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
if err := os.Remove(filepath.Join(cfg.CertificatesDir, "ca.key")); err != nil {
|
||||
return fmt.Errorf("failed removing ca.key: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertIsCa(t *testing.T, cert *x509.Certificate) {
|
||||
if !cert.IsCA {
|
||||
t.Error("cert is not a valida CA")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCertificateFilesMethods(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
setupFunc func(cfg *kubeadmapi.MasterConfiguration) error
|
||||
createFunc func(cfg *kubeadmapi.MasterConfiguration) error
|
||||
expectedFiles []string
|
||||
}{
|
||||
{
|
||||
createFunc: CreatePKIAssets,
|
||||
expectedFiles: []string{
|
||||
kubeadmconstants.CACertName, kubeadmconstants.CAKeyName,
|
||||
kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName,
|
||||
kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName,
|
||||
kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName,
|
||||
kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName,
|
||||
kubeadmconstants.FrontProxyClientCertName, kubeadmconstants.FrontProxyClientKeyName,
|
||||
},
|
||||
},
|
||||
{
|
||||
createFunc: CreateCACertAndKeyfiles,
|
||||
expectedFiles: []string{kubeadmconstants.CACertName, kubeadmconstants.CAKeyName},
|
||||
},
|
||||
{
|
||||
setupFunc: CreateCACertAndKeyfiles,
|
||||
createFunc: CreateAPIServerCertAndKeyFiles,
|
||||
expectedFiles: []string{kubeadmconstants.APIServerCertName, kubeadmconstants.APIServerKeyName},
|
||||
},
|
||||
{
|
||||
setupFunc: CreateCACertAndKeyfiles,
|
||||
createFunc: CreateAPIServerKubeletClientCertAndKeyFiles,
|
||||
expectedFiles: []string{kubeadmconstants.APIServerKubeletClientCertName, kubeadmconstants.APIServerKubeletClientKeyName},
|
||||
},
|
||||
{
|
||||
createFunc: CreateServiceAccountKeyAndPublicKeyFiles,
|
||||
expectedFiles: []string{kubeadmconstants.ServiceAccountPrivateKeyName, kubeadmconstants.ServiceAccountPublicKeyName},
|
||||
},
|
||||
{
|
||||
createFunc: CreateFrontProxyCACertAndKeyFiles,
|
||||
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName},
|
||||
},
|
||||
{
|
||||
setupFunc: CreateFrontProxyCACertAndKeyFiles,
|
||||
createFunc: CreateFrontProxyClientCertAndKeyFiles,
|
||||
expectedFiles: []string{kubeadmconstants.FrontProxyCACertName, kubeadmconstants.FrontProxyCAKeyName},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
CertificatesDir: tmpdir,
|
||||
}
|
||||
|
||||
// executes setup func (if necessary)
|
||||
if test.setupFunc != nil {
|
||||
if err := test.setupFunc(cfg); err != nil {
|
||||
t.Errorf("error executing setupFunc: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// executes create func
|
||||
if err := test.createFunc(cfg); err != nil {
|
||||
t.Errorf("error executing createFunc: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// asserts expected files are there
|
||||
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
|
||||
}
|
||||
}
|
||||
46
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/doc.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2016 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 certs
|
||||
|
||||
/*
|
||||
|
||||
PHASE: CERTIFICATES
|
||||
|
||||
INPUTS:
|
||||
From MasterConfiguration
|
||||
.API.AdvertiseAddress is an optional parameter that can be passed for an extra addition to the SAN IPs
|
||||
.APIServerCertSANs is needed for knowing which DNS names and IPs the API Server serving cert should be valid for
|
||||
.Networking.DNSDomain is needed for knowing which DNS name the internal kubernetes service has
|
||||
.Networking.ServiceSubnet is needed for knowing which IP the internal kubernetes service is going to point to
|
||||
.CertificatesDir is required for knowing where all certificates should be stored
|
||||
|
||||
OUTPUTS:
|
||||
Files to .CertificatesDir (default /etc/kubernetes/pki):
|
||||
- ca.crt
|
||||
- ca.key
|
||||
- apiserver.crt
|
||||
- apiserver.key
|
||||
- apiserver-kubelet-client.crt
|
||||
- apiserver-kubelet-client.key
|
||||
- sa.pub
|
||||
- sa.key
|
||||
- front-proxy-ca.crt
|
||||
- front-proxy-ca.key
|
||||
- front-proxy-client.crt
|
||||
- front-proxy-client.key
|
||||
|
||||
*/
|
||||
33
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/BUILD
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["pki_helpers_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = ["//vendor/k8s.io/client-go/util/cert:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["pki_helpers.go"],
|
||||
deps = ["//vendor/k8s.io/client-go/util/cert:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
246
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go
generated
vendored
Normal file
246
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
Copyright 2016 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 pkiutil
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
func NewCertificateAuthority() (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||
}
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: "kubernetes",
|
||||
}
|
||||
cert, err := certutil.NewSelfSignedCACert(config, key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err)
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||
}
|
||||
|
||||
cert, err := certutil.NewSignedCert(config, key, caCert, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// HasServerAuth returns true if the given certificate is a ServerAuth
|
||||
func HasServerAuth(cert *x509.Certificate) bool {
|
||||
for i := range cert.ExtKeyUsage {
|
||||
if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error {
|
||||
if err := WriteKey(pkiPath, name, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := WriteCert(pkiPath, name, cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteCert(pkiPath, name string, cert *x509.Certificate) error {
|
||||
if cert == nil {
|
||||
return fmt.Errorf("certificate cannot be nil when writing to file")
|
||||
}
|
||||
|
||||
certificatePath := pathForCert(pkiPath, name)
|
||||
if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil {
|
||||
return fmt.Errorf("unable to write certificate to file %q: [%v]", certificatePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteKey(pkiPath, name string, key *rsa.PrivateKey) error {
|
||||
if key == nil {
|
||||
return fmt.Errorf("private key cannot be nil when writing to file")
|
||||
}
|
||||
|
||||
privateKeyPath := pathForKey(pkiPath, name)
|
||||
if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil {
|
||||
return fmt.Errorf("unable to write private key to file %q: [%v]", privateKeyPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error {
|
||||
if key == nil {
|
||||
return fmt.Errorf("public key cannot be nil when writing to file")
|
||||
}
|
||||
|
||||
publicKeyBytes, err := certutil.EncodePublicKeyPEM(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKeyPath := pathForPublicKey(pkiPath, name)
|
||||
if err := certutil.WriteKey(publicKeyPath, publicKeyBytes); err != nil {
|
||||
return fmt.Errorf("unable to write public key to file %q: [%v]", publicKeyPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertOrKeyExist retuns a boolean whether the cert or the key exists
|
||||
func CertOrKeyExist(pkiPath, name string) bool {
|
||||
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
|
||||
|
||||
_, certErr := os.Stat(certificatePath)
|
||||
_, keyErr := os.Stat(privateKeyPath)
|
||||
if os.IsNotExist(certErr) && os.IsNotExist(keyErr) {
|
||||
// The cert or the key did not exist
|
||||
return false
|
||||
}
|
||||
|
||||
// Both files exist or one of them
|
||||
return true
|
||||
}
|
||||
|
||||
// TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid
|
||||
func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
cert, err := TryLoadCertFromDisk(pkiPath, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
key, err := TryLoadKeyFromDisk(pkiPath, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// TryLoadCertFromDisk tries to load the cert from the disk and validates that it is valid
|
||||
func TryLoadCertFromDisk(pkiPath, name string) (*x509.Certificate, error) {
|
||||
certificatePath := pathForCert(pkiPath, name)
|
||||
|
||||
certs, err := certutil.CertsFromFile(certificatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't load the certificate file %s: %v", certificatePath, err)
|
||||
}
|
||||
|
||||
// We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one
|
||||
// TODO: Support multiple certs here in order to be able to rotate certs
|
||||
cert := certs[0]
|
||||
|
||||
// Check so that the certificate is valid now
|
||||
now := time.Now()
|
||||
if now.Before(cert.NotBefore) {
|
||||
return nil, fmt.Errorf("the certificate is not valid yet")
|
||||
}
|
||||
if now.After(cert.NotAfter) {
|
||||
return nil, fmt.Errorf("the certificate has expired")
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// TryLoadKeyFromDisk tries to load the key from the disk and validates that it is valid
|
||||
func TryLoadKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, error) {
|
||||
privateKeyPath := pathForKey(pkiPath, name)
|
||||
|
||||
// Parse the private key from a file
|
||||
privKey, err := certutil.PrivateKeyFromFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't load the private key file %s: %v", privateKeyPath, err)
|
||||
}
|
||||
|
||||
// Allow RSA format only
|
||||
var key *rsa.PrivateKey
|
||||
switch k := privKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
key = k
|
||||
default:
|
||||
return nil, fmt.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// TryLoadPrivatePublicKeyFromDisk tries to load the key from the disk and validates that it is valid
|
||||
func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) {
|
||||
privateKeyPath := pathForKey(pkiPath, name)
|
||||
|
||||
// Parse the private key from a file
|
||||
privKey, err := certutil.PrivateKeyFromFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("couldn't load the private key file %s: %v", privateKeyPath, err)
|
||||
}
|
||||
|
||||
publicKeyPath := pathForPublicKey(pkiPath, name)
|
||||
|
||||
// Parse the public key from a file
|
||||
pubKeys, err := certutil.PublicKeysFromFile(publicKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("couldn't load the public key file %s: %v", publicKeyPath, err)
|
||||
}
|
||||
|
||||
// Allow RSA format only
|
||||
k, ok := privKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("the private key file %s isn't in RSA format", privateKeyPath)
|
||||
}
|
||||
|
||||
p := pubKeys[0].(*rsa.PublicKey)
|
||||
|
||||
return k, p, nil
|
||||
}
|
||||
|
||||
func pathsForCertAndKey(pkiPath, name string) (string, string) {
|
||||
return pathForCert(pkiPath, name), pathForKey(pkiPath, name)
|
||||
}
|
||||
|
||||
func pathForCert(pkiPath, name string) string {
|
||||
return filepath.Join(pkiPath, fmt.Sprintf("%s.crt", name))
|
||||
}
|
||||
|
||||
func pathForKey(pkiPath, name string) string {
|
||||
return filepath.Join(pkiPath, fmt.Sprintf("%s.key", name))
|
||||
}
|
||||
|
||||
func pathForPublicKey(pkiPath, name string) string {
|
||||
return filepath.Join(pkiPath, fmt.Sprintf("%s.pub", name))
|
||||
}
|
||||
434
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go
generated
vendored
Normal file
434
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil/pki_helpers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/*
|
||||
Copyright 2016 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 pkiutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
func TestNewCertificateAuthority(t *testing.T) {
|
||||
cert, key, err := NewCertificateAuthority()
|
||||
|
||||
if cert == nil {
|
||||
t.Errorf(
|
||||
"failed NewCertificateAuthority, cert == nil",
|
||||
)
|
||||
}
|
||||
if key == nil {
|
||||
t.Errorf(
|
||||
"failed NewCertificateAuthority, key == nil",
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed NewCertificateAuthority with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCertAndKey(t *testing.T) {
|
||||
var tests = []struct {
|
||||
caKeySize int
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
// RSA key too small
|
||||
caKeySize: 128,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
// Should succeed
|
||||
caKeySize: 2048,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, rt.caKeySize)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create rsa Private Key")
|
||||
}
|
||||
caCert := &x509.Certificate{}
|
||||
config := certutil.Config{
|
||||
CommonName: "test",
|
||||
Organization: []string{"test"},
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
_, _, actual := NewCertAndKey(caCert, caKey, config)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed NewCertAndKey:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasServerAuth(t *testing.T) {
|
||||
caCert, caKey, _ := NewCertificateAuthority()
|
||||
|
||||
var tests = []struct {
|
||||
config certutil.Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
config: certutil.Config{
|
||||
CommonName: "test",
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
config: certutil.Config{
|
||||
CommonName: "test",
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
cert, _, err := NewCertAndKey(caCert, caKey, rt.config)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create cert: %v", err)
|
||||
}
|
||||
actual := HasServerAuth(cert)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed HasServerAuth:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCertAndKey(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create rsa Private Key")
|
||||
}
|
||||
caCert := &x509.Certificate{}
|
||||
actual := WriteCertAndKey(tmpdir, "foo", caCert, caKey)
|
||||
if actual != nil {
|
||||
t.Errorf(
|
||||
"failed WriteCertAndKey with an error: %v",
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCert(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caCert := &x509.Certificate{}
|
||||
actual := WriteCert(tmpdir, "foo", caCert)
|
||||
if actual != nil {
|
||||
t.Errorf(
|
||||
"failed WriteCertAndKey with an error: %v",
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteKey(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create rsa Private Key")
|
||||
}
|
||||
actual := WriteKey(tmpdir, "foo", caKey)
|
||||
if actual != nil {
|
||||
t.Errorf(
|
||||
"failed WriteCertAndKey with an error: %v",
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWritePublicKey(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create rsa Private Key")
|
||||
}
|
||||
actual := WritePublicKey(tmpdir, "foo", &caKey.PublicKey)
|
||||
if actual != nil {
|
||||
t.Errorf(
|
||||
"failed WriteCertAndKey with an error: %v",
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertOrKeyExist(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create rsa Private Key")
|
||||
}
|
||||
caCert := &x509.Certificate{}
|
||||
actual := WriteCertAndKey(tmpdir, "foo", caCert, caKey)
|
||||
if actual != nil {
|
||||
t.Errorf(
|
||||
"failed WriteCertAndKey with an error: %v",
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
path string
|
||||
name string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
name: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
path: tmpdir,
|
||||
name: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := CertOrKeyExist(rt.path, rt.name)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed CertOrKeyExist:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryLoadCertAndKeyFromDisk(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caCert, caKey, err := NewCertificateAuthority()
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to create cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
err = WriteCertAndKey(tmpdir, "foo", caCert, caKey)
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to write cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
path string
|
||||
name string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
name: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
path: tmpdir,
|
||||
name: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, _, actual := TryLoadCertAndKeyFromDisk(rt.path, rt.name)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryLoadCertFromDisk(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
caCert, _, err := NewCertificateAuthority()
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to create cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
err = WriteCert(tmpdir, "foo", caCert)
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to write cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
path string
|
||||
name string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
name: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
path: tmpdir,
|
||||
name: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, actual := TryLoadCertFromDisk(rt.path, rt.name)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryLoadKeyFromDisk(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
_, caKey, err := NewCertificateAuthority()
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to create cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
err = WriteKey(tmpdir, "foo", caKey)
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"failed to write cert and key with an error: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
path string
|
||||
name string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
name: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
path: tmpdir,
|
||||
name: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
_, actual := TryLoadKeyFromDisk(rt.path, rt.name)
|
||||
if (actual == nil) != rt.expected {
|
||||
t.Errorf(
|
||||
"failed TryLoadCertAndKeyFromDisk:\n\texpected: %t\n\t actual: %t",
|
||||
rt.expected,
|
||||
(actual == nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathsForCertAndKey(t *testing.T) {
|
||||
crtPath, keyPath := pathsForCertAndKey("/foo", "bar")
|
||||
if crtPath != "/foo/bar.crt" {
|
||||
t.Errorf("unexpected certificate path: %s", crtPath)
|
||||
}
|
||||
if keyPath != "/foo/bar.key" {
|
||||
t.Errorf("unexpected key path: %s", keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathForCert(t *testing.T) {
|
||||
crtPath := pathForCert("/foo", "bar")
|
||||
if crtPath != "/foo/bar.crt" {
|
||||
t.Errorf("unexpected certificate path: %s", crtPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathForKey(t *testing.T) {
|
||||
keyPath := pathForKey("/foo", "bar")
|
||||
if keyPath != "/foo/bar.key" {
|
||||
t.Errorf("unexpected certificate path: %s", keyPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathForPublicKey(t *testing.T) {
|
||||
pubPath := pathForPublicKey("/foo", "bar")
|
||||
if pubPath != "/foo/bar.pub" {
|
||||
t.Errorf("unexpected certificate path: %s", pubPath)
|
||||
}
|
||||
}
|
||||
58
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/BUILD
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"manifests_test.go",
|
||||
"volumes_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"manifests.go",
|
||||
"volumes.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/staticpod:go_default_library",
|
||||
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
307
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/manifests.go
generated
vendored
Normal file
307
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/manifests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
Copyright 2016 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 controlplane
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
// Static pod definitions in golang form are included below so that `kubeadm init` can get going.
|
||||
const (
|
||||
DefaultCloudConfigPath = "/etc/kubernetes/cloud-config"
|
||||
|
||||
defaultv17AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota"
|
||||
)
|
||||
|
||||
// CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane.
|
||||
func CreateInitStaticPodManifestFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler)
|
||||
}
|
||||
|
||||
// CreateAPIServerStaticPodManifestFile will write APIserver static pod manifest file.
|
||||
func CreateAPIServerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeAPIServer)
|
||||
}
|
||||
|
||||
// CreateControllerManagerStaticPodManifestFile will write controller manager static pod manifest file.
|
||||
func CreateControllerManagerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeControllerManager)
|
||||
}
|
||||
|
||||
// CreateSchedulerStaticPodManifestFile will write scheduler static pod manifest file.
|
||||
func CreateSchedulerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeScheduler)
|
||||
}
|
||||
|
||||
// GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current MasterConfiguration
|
||||
// NB. this methods holds the information about how kubeadm creates static pod mainfests.
|
||||
func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) map[string]v1.Pod {
|
||||
|
||||
// Get the required hostpath mounts
|
||||
mounts := getHostPathVolumesForTheControlPlane(cfg)
|
||||
|
||||
// Prepare static pod specs
|
||||
staticPodSpecs := map[string]v1.Pod{
|
||||
kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeAPIServer,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getAPIServerCommand(cfg, k8sVersion),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
|
||||
Resources: staticpodutil.ComponentResources("250m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
|
||||
kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeControllerManager,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getControllerManagerCommand(cfg, k8sVersion),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(10252, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: staticpodutil.ComponentResources("200m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
|
||||
kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeScheduler,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getSchedulerCommand(cfg),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(10251, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: staticpodutil.ComponentResources("100m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
|
||||
}
|
||||
|
||||
return staticPodSpecs
|
||||
}
|
||||
|
||||
// createStaticPodFiles creates all the requested static pod files.
|
||||
func createStaticPodFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration, componentNames ...string) error {
|
||||
|
||||
// TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string
|
||||
k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// gets the StaticPodSpecs, actualized for the current MasterConfiguration
|
||||
specs := GetStaticPodSpecs(cfg, k8sVersion)
|
||||
|
||||
// creates required static pod specs
|
||||
for _, componentName := range componentNames {
|
||||
// retrives the StaticPodSpec for given component
|
||||
spec, exists := specs[componentName]
|
||||
if !exists {
|
||||
return fmt.Errorf("couldn't retrive StaticPodSpec for %s", componentName)
|
||||
}
|
||||
|
||||
// writes the StaticPodSpec to disk
|
||||
if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
|
||||
return fmt.Errorf("failed to create static pod manifest file for %q: %v", componentName, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[controlplane] Wrote Static Pod manifest for component %s to %q\n", componentName, kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAPIServerCommand builds the right API server command from the given config object and version
|
||||
func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string {
|
||||
defaultArguments := map[string]string{
|
||||
"advertise-address": cfg.API.AdvertiseAddress,
|
||||
"insecure-port": "0",
|
||||
"admission-control": defaultv17AdmissionControl,
|
||||
"service-cluster-ip-range": cfg.Networking.ServiceSubnet,
|
||||
"service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
|
||||
"client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
|
||||
"tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
|
||||
"kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
|
||||
"kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
|
||||
"secure-port": fmt.Sprintf("%d", cfg.API.BindPort),
|
||||
"allow-privileged": "true",
|
||||
"kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
|
||||
// add options to configure the front proxy. Without the generated client cert, this will never be useable
|
||||
// so add it unconditionally with recommended values
|
||||
"requestheader-username-headers": "X-Remote-User",
|
||||
"requestheader-group-headers": "X-Remote-Group",
|
||||
"requestheader-extra-headers-prefix": "X-Remote-Extra-",
|
||||
"requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
|
||||
"requestheader-allowed-names": "front-proxy-client",
|
||||
"proxy-client-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientCertName),
|
||||
"proxy-client-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName),
|
||||
}
|
||||
|
||||
command := []string{"kube-apiserver"}
|
||||
|
||||
// Note: Mutating defaultArguments dynamically must happen before the BuildArgumentListFromMap call below
|
||||
if k8sVersion.AtLeast(kubeadmconstants.UseEnableBootstrapTokenAuthFlagVersion) {
|
||||
defaultArguments["enable-bootstrap-token-auth"] = "true"
|
||||
} else {
|
||||
defaultArguments["experimental-bootstrap-token-auth"] = "true"
|
||||
}
|
||||
|
||||
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServerExtraArgs)...)
|
||||
command = append(command, getAuthzParameters(cfg.AuthorizationModes)...)
|
||||
|
||||
// Check if the user decided to use an external etcd cluster
|
||||
if len(cfg.Etcd.Endpoints) > 0 {
|
||||
command = append(command, fmt.Sprintf("--etcd-servers=%s", strings.Join(cfg.Etcd.Endpoints, ",")))
|
||||
} else {
|
||||
command = append(command, "--etcd-servers=http://127.0.0.1:2379")
|
||||
}
|
||||
|
||||
// Is etcd secured?
|
||||
if cfg.Etcd.CAFile != "" {
|
||||
command = append(command, fmt.Sprintf("--etcd-cafile=%s", cfg.Etcd.CAFile))
|
||||
}
|
||||
if cfg.Etcd.CertFile != "" && cfg.Etcd.KeyFile != "" {
|
||||
etcdClientFileArg := fmt.Sprintf("--etcd-certfile=%s", cfg.Etcd.CertFile)
|
||||
etcdKeyFileArg := fmt.Sprintf("--etcd-keyfile=%s", cfg.Etcd.KeyFile)
|
||||
command = append(command, etcdClientFileArg, etcdKeyFileArg)
|
||||
}
|
||||
|
||||
if cfg.CloudProvider != "" {
|
||||
command = append(command, "--cloud-provider="+cfg.CloudProvider)
|
||||
|
||||
// Only append the --cloud-config option if there's a such file
|
||||
if _, err := os.Stat(DefaultCloudConfigPath); err == nil {
|
||||
command = append(command, "--cloud-config="+DefaultCloudConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// getControllerManagerCommand builds the right controller manager command from the given config object and version
|
||||
func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string {
|
||||
|
||||
defaultArguments := map[string]string{
|
||||
"address": "127.0.0.1",
|
||||
"leader-elect": "true",
|
||||
"kubeconfig": filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName),
|
||||
"root-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"service-account-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
|
||||
"cluster-signing-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
|
||||
"cluster-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
|
||||
"use-service-account-credentials": "true",
|
||||
"controllers": "*,bootstrapsigner,tokencleaner",
|
||||
}
|
||||
|
||||
// If using external CA, pass empty string to controller manager instead of ca.key/ca.crt path,
|
||||
// so that the csrsigning controller fails to start
|
||||
if res, _ := certphase.UsingExternalCA(cfg); res {
|
||||
defaultArguments["cluster-signing-key-file"] = ""
|
||||
defaultArguments["cluster-signing-cert-file"] = ""
|
||||
}
|
||||
|
||||
command := []string{"kube-controller-manager"}
|
||||
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.ControllerManagerExtraArgs)...)
|
||||
|
||||
if cfg.CloudProvider != "" {
|
||||
command = append(command, "--cloud-provider="+cfg.CloudProvider)
|
||||
|
||||
// Only append the --cloud-config option if there's a such file
|
||||
if _, err := os.Stat(DefaultCloudConfigPath); err == nil {
|
||||
command = append(command, "--cloud-config="+DefaultCloudConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Let the controller-manager allocate Node CIDRs for the Pod network.
|
||||
// Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
|
||||
if cfg.Networking.PodSubnet != "" {
|
||||
maskSize := "24"
|
||||
if ip, _, err := net.ParseCIDR(cfg.Networking.PodSubnet); err == nil {
|
||||
if ip.To4() == nil {
|
||||
maskSize = "64"
|
||||
}
|
||||
}
|
||||
command = append(command, "--allocate-node-cidrs=true", "--cluster-cidr="+cfg.Networking.PodSubnet,
|
||||
"--node-cidr-mask-size="+maskSize)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// getSchedulerCommand builds the right scheduler command from the given config object and version
|
||||
func getSchedulerCommand(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||
defaultArguments := map[string]string{
|
||||
"address": "127.0.0.1",
|
||||
"leader-elect": "true",
|
||||
"kubeconfig": filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName),
|
||||
}
|
||||
|
||||
command := []string{"kube-scheduler"}
|
||||
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.SchedulerExtraArgs)...)
|
||||
return command
|
||||
}
|
||||
|
||||
// getProxyEnvVars builds a list of environment variables to use in the control plane containers in order to use the right proxy
|
||||
func getProxyEnvVars() []v1.EnvVar {
|
||||
envs := []v1.EnvVar{}
|
||||
for _, env := range os.Environ() {
|
||||
pos := strings.Index(env, "=")
|
||||
if pos == -1 {
|
||||
// malformed environment variable, skip it.
|
||||
continue
|
||||
}
|
||||
name := env[:pos]
|
||||
value := env[pos+1:]
|
||||
if strings.HasSuffix(strings.ToLower(name), "_proxy") && value != "" {
|
||||
envVar := v1.EnvVar{Name: name, Value: value}
|
||||
envs = append(envs, envVar)
|
||||
}
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// getAuthzParameters gets the authorization-related parameters to the api server
|
||||
// At this point, we can assume the list of authorization modes is valid (due to that it has been validated in the API machinery code already)
|
||||
// If the list is empty; it's defaulted (mostly for unit testing)
|
||||
func getAuthzParameters(modes []string) []string {
|
||||
command := []string{}
|
||||
strset := sets.NewString(modes...)
|
||||
|
||||
if len(modes) == 0 {
|
||||
return []string{fmt.Sprintf("--authorization-mode=%s", kubeadmapiext.DefaultAuthorizationModes)}
|
||||
}
|
||||
|
||||
if strset.Has(authzmodes.ModeABAC) {
|
||||
command = append(command, "--authorization-policy-file="+kubeadmconstants.AuthorizationPolicyPath)
|
||||
}
|
||||
if strset.Has(authzmodes.ModeWebhook) {
|
||||
command = append(command, "--authorization-webhook-config-file="+kubeadmconstants.AuthorizationWebhookConfigPath)
|
||||
}
|
||||
|
||||
command = append(command, "--authorization-mode="+strings.Join(modes, ","))
|
||||
return command
|
||||
}
|
||||
643
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/manifests_test.go
generated
vendored
Normal file
643
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/manifests_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
Copyright 2016 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 controlplane
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
)
|
||||
|
||||
const (
|
||||
testCertsDir = "/var/lib/certs"
|
||||
etcdDataDir = "/var/lib/etcd"
|
||||
)
|
||||
|
||||
func TestGetStaticPodSpecs(t *testing.T) {
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Executes GetStaticPodSpecs
|
||||
|
||||
// TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string
|
||||
k8sVersion, _ := version.ParseSemantic(cfg.KubernetesVersion)
|
||||
|
||||
specs := GetStaticPodSpecs(cfg, k8sVersion)
|
||||
|
||||
var assertions = []struct {
|
||||
staticPodName string
|
||||
}{
|
||||
{
|
||||
staticPodName: kubeadmconstants.KubeAPIServer,
|
||||
},
|
||||
{
|
||||
staticPodName: kubeadmconstants.KubeControllerManager,
|
||||
},
|
||||
{
|
||||
staticPodName: kubeadmconstants.KubeScheduler,
|
||||
},
|
||||
}
|
||||
|
||||
for _, assertion := range assertions {
|
||||
|
||||
// assert the spec for the staticPodName exists
|
||||
if spec, ok := specs[assertion.staticPodName]; ok {
|
||||
|
||||
// Assert each specs refers to the right pod
|
||||
if spec.Spec.Containers[0].Name != assertion.staticPodName {
|
||||
t.Errorf("getKubeConfigSpecs spec for %s contains pod %s, expectes %s", assertion.staticPodName, spec.Spec.Containers[0].Name, assertion.staticPodName)
|
||||
}
|
||||
|
||||
} else {
|
||||
t.Errorf("getStaticPodSpecs didn't create spec for %s ", assertion.staticPodName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateStaticPodFilesAndWrappers(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
createStaticPodFunction func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
|
||||
expectedFiles []string
|
||||
}{
|
||||
{ // CreateInitStaticPodManifestFiles
|
||||
createStaticPodFunction: CreateInitStaticPodManifestFiles,
|
||||
expectedFiles: []string{kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler},
|
||||
},
|
||||
{ // CreateAPIServerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateAPIServerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeAPIServer},
|
||||
},
|
||||
{ // CreateControllerManagerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateControllerManagerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeControllerManager},
|
||||
},
|
||||
{ // CreateSchedulerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateSchedulerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeScheduler},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Execute createStaticPodFunction
|
||||
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
|
||||
err := test.createStaticPodFunction(manifestPath, cfg)
|
||||
if err != nil {
|
||||
t.Errorf("Error executing createStaticPodFunction: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Assert expected files are there
|
||||
testutil.AssertFilesCount(t, manifestPath, len(test.expectedFiles))
|
||||
|
||||
for _, fileName := range test.expectedFiles {
|
||||
testutil.AssertFileExists(t, manifestPath, fileName+".yaml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAPIServerCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--secure-port=123",
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=1.2.3.4",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.8.0-beta.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--secure-port=123",
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--enable-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=1.2.3.4",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.1",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--secure-port=123",
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=4.3.2.1",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
Etcd: kubeadmapi.Etcd{CertFile: "fiz", KeyFile: "faz"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.2",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
"--secure-port=123",
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=4.3.2.1",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
"--etcd-certfile=fiz",
|
||||
"--etcd-keyfile=faz",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "4.3.2.1"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
Etcd: kubeadmapi.Etcd{CertFile: "fiz", KeyFile: "faz"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.3",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
fmt.Sprintf("--secure-port=%d", 123),
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=4.3.2.1",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
"--etcd-certfile=fiz",
|
||||
"--etcd-keyfile=faz",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{BindPort: 123, AdvertiseAddress: "2001:db8::1"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
|
||||
Etcd: kubeadmapi.Etcd{CertFile: "fiz", KeyFile: "faz"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-apiserver",
|
||||
"--insecure-port=0",
|
||||
"--admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota",
|
||||
"--service-cluster-ip-range=bar",
|
||||
"--service-account-key-file=" + testCertsDir + "/sa.pub",
|
||||
"--client-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--tls-cert-file=" + testCertsDir + "/apiserver.crt",
|
||||
"--tls-private-key-file=" + testCertsDir + "/apiserver.key",
|
||||
"--kubelet-client-certificate=" + testCertsDir + "/apiserver-kubelet-client.crt",
|
||||
"--kubelet-client-key=" + testCertsDir + "/apiserver-kubelet-client.key",
|
||||
fmt.Sprintf("--secure-port=%d", 123),
|
||||
"--allow-privileged=true",
|
||||
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
|
||||
"--experimental-bootstrap-token-auth=true",
|
||||
"--proxy-client-cert-file=/var/lib/certs/front-proxy-client.crt",
|
||||
"--proxy-client-key-file=/var/lib/certs/front-proxy-client.key",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-client-ca-file=" + testCertsDir + "/front-proxy-ca.crt",
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
"--advertise-address=2001:db8::1",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
"--etcd-certfile=fiz",
|
||||
"--etcd-keyfile=faz",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getAPIServerCommand(rt.cfg, version.MustParseSemantic(rt.cfg.KubernetesVersion))
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getAPIServerCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetControllerManagerCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
CloudProvider: "foo",
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--cloud-provider=foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Networking: kubeadmapi.Networking{PodSubnet: "10.0.1.15/16"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--allocate-node-cidrs=true",
|
||||
"--cluster-cidr=10.0.1.15/16",
|
||||
"--node-cidr-mask-size=24",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Networking: kubeadmapi.Networking{PodSubnet: "2001:101:115::/48"},
|
||||
CertificatesDir: testCertsDir,
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expected: []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + testCertsDir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + testCertsDir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + testCertsDir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + testCertsDir + "/ca.key",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--allocate-node-cidrs=true",
|
||||
"--cluster-cidr=2001:101:115::/48",
|
||||
"--node-cidr-mask-size=64",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getControllerManagerCommand(rt.cfg, version.MustParseSemantic(rt.cfg.KubernetesVersion))
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getControllerManagerCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetControllerManagerCommandExternalCA(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
caKeyPresent bool
|
||||
expectedArgFunc func(dir string) []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
},
|
||||
caKeyPresent: false,
|
||||
expectedArgFunc: func(tmpdir string) []string {
|
||||
return []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + tmpdir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + tmpdir + "/sa.key",
|
||||
"--cluster-signing-cert-file=",
|
||||
"--cluster-signing-key-file=",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4"},
|
||||
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
|
||||
NodeName: "valid-hostname",
|
||||
},
|
||||
caKeyPresent: true,
|
||||
expectedArgFunc: func(tmpdir string) []string {
|
||||
return []string{
|
||||
"kube-controller-manager",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/controller-manager.conf",
|
||||
"--root-ca-file=" + tmpdir + "/ca.crt",
|
||||
"--service-account-private-key-file=" + tmpdir + "/sa.key",
|
||||
"--cluster-signing-cert-file=" + tmpdir + "/ca.crt",
|
||||
"--cluster-signing-key-file=" + tmpdir + "/ca.key",
|
||||
"--use-service-account-credentials=true",
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
test.cfg.CertificatesDir = tmpdir
|
||||
|
||||
if err := certs.CreatePKIAssets(test.cfg); err != nil {
|
||||
t.Errorf("failed creating pki assets: %v", err)
|
||||
}
|
||||
|
||||
// delete ca.key if test.caKeyPresent is false
|
||||
if !test.caKeyPresent {
|
||||
if err := os.Remove(filepath.Join(test.cfg.CertificatesDir, "ca.key")); err != nil {
|
||||
t.Errorf("failed removing ca.key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := getControllerManagerCommand(test.cfg, version.MustParseSemantic(test.cfg.KubernetesVersion))
|
||||
expected := test.expectedArgFunc(tmpdir)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(expected)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("failed getControllerManagerCommand:\nexpected:\n%v\nsaw:\n%v", expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSchedulerCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{},
|
||||
expected: []string{
|
||||
"kube-scheduler",
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect=true",
|
||||
"--kubeconfig=" + kubeadmconstants.KubernetesDir + "/scheduler.conf",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getSchedulerCommand(rt.cfg)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getSchedulerCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthzParameters(t *testing.T) {
|
||||
var tests = []struct {
|
||||
authMode []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
authMode: []string{},
|
||||
expected: []string{
|
||||
"--authorization-mode=Node,RBAC",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"RBAC"},
|
||||
expected: []string{
|
||||
"--authorization-mode=RBAC",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"AlwaysAllow"},
|
||||
expected: []string{
|
||||
"--authorization-mode=AlwaysAllow",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"AlwaysDeny"},
|
||||
expected: []string{
|
||||
"--authorization-mode=AlwaysDeny",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"ABAC"},
|
||||
expected: []string{
|
||||
"--authorization-mode=ABAC",
|
||||
"--authorization-policy-file=/etc/kubernetes/abac_policy.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"ABAC", "Webhook"},
|
||||
expected: []string{
|
||||
"--authorization-mode=ABAC,Webhook",
|
||||
"--authorization-policy-file=/etc/kubernetes/abac_policy.json",
|
||||
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"ABAC", "RBAC", "Webhook"},
|
||||
expected: []string{
|
||||
"--authorization-mode=ABAC,RBAC,Webhook",
|
||||
"--authorization-policy-file=/etc/kubernetes/abac_policy.json",
|
||||
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf",
|
||||
},
|
||||
},
|
||||
{
|
||||
authMode: []string{"Node", "RBAC", "Webhook", "ABAC"},
|
||||
expected: []string{
|
||||
"--authorization-mode=Node,RBAC,Webhook,ABAC",
|
||||
"--authorization-policy-file=/etc/kubernetes/abac_policy.json",
|
||||
"--authorization-webhook-config-file=/etc/kubernetes/webhook_authz.conf",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getAuthzParameters(rt.authMode)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getAuthzParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
170
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/volumes.go
generated
vendored
Normal file
170
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/volumes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
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 controlplane
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
const (
|
||||
caCertsVolumeName = "ca-certs"
|
||||
caCertsVolumePath = "/etc/ssl/certs"
|
||||
caCertsPkiVolumeName = "ca-certs-etc-pki"
|
||||
flexvolumeDirVolumeName = "flexvolume-dir"
|
||||
flexvolumeDirVolumePath = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec"
|
||||
)
|
||||
|
||||
// caCertsPkiVolumePath specifies the path that can be conditionally mounted into the apiserver and controller-manager containers
|
||||
// as /etc/ssl/certs might be a symlink to it. It's a variable since it may be changed in unit testing. This var MUST NOT be changed
|
||||
// in normal codepaths during runtime.
|
||||
var caCertsPkiVolumePath = "/etc/pki"
|
||||
|
||||
// getHostPathVolumesForTheControlPlane gets the required hostPath volumes and mounts for the control plane
|
||||
func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.MasterConfiguration) controlPlaneHostPathMounts {
|
||||
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
|
||||
hostPathFileOrCreate := v1.HostPathFileOrCreate
|
||||
mounts := newControlPlaneHostPathMounts()
|
||||
|
||||
// HostPath volumes for the API Server
|
||||
// Read-only mount for the certificates directory
|
||||
// TODO: Always mount the K8s Certificates directory to a static path inside of the container
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true, &hostPathDirectoryOrCreate)
|
||||
// Read-only mount for the ca certs (/etc/ssl/certs) directory
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true, &hostPathDirectoryOrCreate)
|
||||
|
||||
// If external etcd is specified, mount the directories needed for accessing the CA/serving certs and the private key
|
||||
if len(cfg.Etcd.Endpoints) != 0 {
|
||||
etcdVols, etcdVolMounts := getEtcdCertVolumes(cfg.Etcd)
|
||||
mounts.AddHostPathMounts(kubeadmconstants.KubeAPIServer, etcdVols, etcdVolMounts)
|
||||
}
|
||||
|
||||
// HostPath volumes for the controller manager
|
||||
// Read-only mount for the certificates directory
|
||||
// TODO: Always mount the K8s Certificates directory to a static path inside of the container
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true, &hostPathDirectoryOrCreate)
|
||||
// Read-only mount for the ca certs (/etc/ssl/certs) directory
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true, &hostPathDirectoryOrCreate)
|
||||
// Read-only mount for the controller manager kubeconfig file
|
||||
controllerManagerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true, &hostPathFileOrCreate)
|
||||
// Mount for the flexvolume directory (/usr/libexec/kubernetes/kubelet-plugins/volume/exec) directory
|
||||
// Flexvolume dir must NOT be readonly as it is used for third-party plugins to integrate with their storage backends via unix domain socket.
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate)
|
||||
|
||||
// HostPath volumes for the scheduler
|
||||
// Read-only mount for the scheduler kubeconfig file
|
||||
schedulerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeScheduler, kubeadmconstants.KubeConfigVolumeName, schedulerKubeConfigFile, schedulerKubeConfigFile, true, &hostPathFileOrCreate)
|
||||
|
||||
// On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
|
||||
// due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
|
||||
if isPkiVolumeMountNeeded() {
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsPkiVolumeName, caCertsPkiVolumePath, caCertsPkiVolumePath, true, &hostPathDirectoryOrCreate)
|
||||
mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsPkiVolumeName, caCertsPkiVolumePath, caCertsPkiVolumePath, true, &hostPathDirectoryOrCreate)
|
||||
}
|
||||
|
||||
return mounts
|
||||
}
|
||||
|
||||
// controlPlaneHostPathMounts is a helper struct for handling all the control plane's hostPath mounts in an easy way
|
||||
type controlPlaneHostPathMounts struct {
|
||||
volumes map[string][]v1.Volume
|
||||
volumeMounts map[string][]v1.VolumeMount
|
||||
}
|
||||
|
||||
func newControlPlaneHostPathMounts() controlPlaneHostPathMounts {
|
||||
return controlPlaneHostPathMounts{
|
||||
volumes: map[string][]v1.Volume{},
|
||||
volumeMounts: map[string][]v1.VolumeMount{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) NewHostPathMount(component, mountName, hostPath, containerPath string, readOnly bool, hostPathType *v1.HostPathType) {
|
||||
c.volumes[component] = append(c.volumes[component], staticpodutil.NewVolume(mountName, hostPath, hostPathType))
|
||||
c.volumeMounts[component] = append(c.volumeMounts[component], staticpodutil.NewVolumeMount(mountName, containerPath, readOnly))
|
||||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) AddHostPathMounts(component string, vols []v1.Volume, volMounts []v1.VolumeMount) {
|
||||
c.volumes[component] = append(c.volumes[component], vols...)
|
||||
c.volumeMounts[component] = append(c.volumeMounts[component], volMounts...)
|
||||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) GetVolumes(component string) []v1.Volume {
|
||||
return c.volumes[component]
|
||||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) []v1.VolumeMount {
|
||||
return c.volumeMounts[component]
|
||||
}
|
||||
|
||||
// getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster
|
||||
func getEtcdCertVolumes(etcdCfg kubeadmapi.Etcd) ([]v1.Volume, []v1.VolumeMount) {
|
||||
certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile}
|
||||
certDirs := sets.NewString()
|
||||
for _, certPath := range certPaths {
|
||||
certDir := filepath.Dir(certPath)
|
||||
// Ignore ".", which is the result of passing an empty path.
|
||||
// Also ignore the cert directories that already may be mounted; /etc/ssl/certs and /etc/pki. If the etcd certs are in there, it's okay, we don't have to do anything
|
||||
if certDir == "." || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, caCertsPkiVolumePath) {
|
||||
continue
|
||||
}
|
||||
// Filter out any existing hostpath mounts in the list that contains a subset of the path
|
||||
alreadyExists := false
|
||||
for _, existingCertDir := range certDirs.List() {
|
||||
// If the current directory is a parent of an existing one, remove the already existing one
|
||||
if strings.HasPrefix(existingCertDir, certDir) {
|
||||
certDirs.Delete(existingCertDir)
|
||||
} else if strings.HasPrefix(certDir, existingCertDir) {
|
||||
// If an existing directory is a parent of the current one, don't add the current one
|
||||
alreadyExists = true
|
||||
}
|
||||
}
|
||||
if alreadyExists {
|
||||
continue
|
||||
}
|
||||
certDirs.Insert(certDir)
|
||||
}
|
||||
|
||||
volumes := []v1.Volume{}
|
||||
volumeMounts := []v1.VolumeMount{}
|
||||
pathType := v1.HostPathDirectoryOrCreate
|
||||
for i, certDir := range certDirs.List() {
|
||||
name := fmt.Sprintf("etcd-certs-%d", i)
|
||||
volumes = append(volumes, staticpodutil.NewVolume(name, certDir, &pathType))
|
||||
volumeMounts = append(volumeMounts, staticpodutil.NewVolumeMount(name, certDir, true))
|
||||
}
|
||||
return volumes, volumeMounts
|
||||
}
|
||||
|
||||
// isPkiVolumeMountNeeded specifies whether /etc/pki should be host-mounted into the containers
|
||||
// On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
|
||||
// due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
|
||||
func isPkiVolumeMountNeeded() bool {
|
||||
if _, err := os.Stat(caCertsPkiVolumePath); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
561
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/volumes_test.go
generated
vendored
Normal file
561
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane/volumes_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,561 @@
|
|||
/*
|
||||
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 controlplane
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestGetEtcdCertVolumes(t *testing.T) {
|
||||
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
|
||||
var tests = []struct {
|
||||
ca, cert, key string
|
||||
vol []v1.Volume
|
||||
volMount []v1.VolumeMount
|
||||
}{
|
||||
{
|
||||
// Should ignore files in /etc/ssl/certs
|
||||
ca: "/etc/ssl/certs/my-etcd-ca.crt",
|
||||
cert: "/etc/ssl/certs/my-etcd.crt",
|
||||
key: "/etc/ssl/certs/my-etcd.key",
|
||||
vol: []v1.Volume{},
|
||||
volMount: []v1.VolumeMount{},
|
||||
},
|
||||
{
|
||||
// Should ignore files in subdirs of /etc/ssl/certs
|
||||
ca: "/etc/ssl/certs/etcd/my-etcd-ca.crt",
|
||||
cert: "/etc/ssl/certs/etcd/my-etcd.crt",
|
||||
key: "/etc/ssl/certs/etcd/my-etcd.key",
|
||||
vol: []v1.Volume{},
|
||||
volMount: []v1.VolumeMount{},
|
||||
},
|
||||
{
|
||||
// Should ignore files in /etc/pki
|
||||
ca: "/etc/pki/my-etcd-ca.crt",
|
||||
cert: "/etc/pki/my-etcd.crt",
|
||||
key: "/etc/pki/my-etcd.key",
|
||||
vol: []v1.Volume{},
|
||||
volMount: []v1.VolumeMount{},
|
||||
},
|
||||
{
|
||||
// All in the same dir
|
||||
ca: "/var/lib/certs/etcd/my-etcd-ca.crt",
|
||||
cert: "/var/lib/certs/etcd/my-etcd.crt",
|
||||
key: "/var/lib/certs/etcd/my-etcd.key",
|
||||
vol: []v1.Volume{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/lib/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: []v1.VolumeMount{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/var/lib/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// One file + two files in separate dirs
|
||||
ca: "/etc/certs/etcd/my-etcd-ca.crt",
|
||||
cert: "/var/lib/certs/etcd/my-etcd.crt",
|
||||
key: "/var/lib/certs/etcd/my-etcd.key",
|
||||
vol: []v1.Volume{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/lib/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: []v1.VolumeMount{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/etc/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
MountPath: "/var/lib/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// All three files in different directories
|
||||
ca: "/etc/certs/etcd/my-etcd-ca.crt",
|
||||
cert: "/var/lib/certs/etcd/my-etcd.crt",
|
||||
key: "/var/lib/certs/private/my-etcd.key",
|
||||
vol: []v1.Volume{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/lib/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-2",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/lib/certs/private",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: []v1.VolumeMount{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/etc/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
MountPath: "/var/lib/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-2",
|
||||
MountPath: "/var/lib/certs/private",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// The most top-level dir should be used
|
||||
ca: "/etc/certs/etcd/my-etcd-ca.crt",
|
||||
cert: "/etc/certs/etcd/serving/my-etcd.crt",
|
||||
key: "/etc/certs/etcd/serving/my-etcd.key",
|
||||
vol: []v1.Volume{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: []v1.VolumeMount{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/etc/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// The most top-level dir should be used, regardless of order
|
||||
ca: "/etc/certs/etcd/ca/my-etcd-ca.crt",
|
||||
cert: "/etc/certs/etcd/my-etcd.crt",
|
||||
key: "/etc/certs/etcd/my-etcd.key",
|
||||
vol: []v1.Volume{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: []v1.VolumeMount{
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/etc/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actualVol, actualVolMount := getEtcdCertVolumes(kubeadmapi.Etcd{
|
||||
CAFile: rt.ca,
|
||||
CertFile: rt.cert,
|
||||
KeyFile: rt.key,
|
||||
})
|
||||
if !reflect.DeepEqual(actualVol, rt.vol) {
|
||||
t.Errorf(
|
||||
"failed getEtcdCertVolumes:\n\texpected: %v\n\t actual: %v",
|
||||
rt.vol,
|
||||
actualVol,
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(actualVolMount, rt.volMount) {
|
||||
t.Errorf(
|
||||
"failed getEtcdCertVolumes:\n\texpected: %v\n\t actual: %v",
|
||||
rt.volMount,
|
||||
actualVolMount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostPathVolumesForTheControlPlane(t *testing.T) {
|
||||
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
|
||||
hostPathFileOrCreate := v1.HostPathFileOrCreate
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
vol map[string][]v1.Volume
|
||||
volMount map[string][]v1.VolumeMount
|
||||
}{
|
||||
{
|
||||
// Should ignore files in /etc/ssl/certs
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
CertificatesDir: testCertsDir,
|
||||
Etcd: kubeadmapi.Etcd{},
|
||||
},
|
||||
vol: map[string][]v1.Volume{
|
||||
kubeadmconstants.KubeAPIServer: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: testCertsDir,
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeControllerManager: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: testCertsDir,
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/controller-manager.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "flexvolume-dir",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeScheduler: {
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/scheduler.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: map[string][]v1.VolumeMount{
|
||||
kubeadmconstants.KubeAPIServer: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: testCertsDir,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeControllerManager: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: testCertsDir,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/controller-manager.conf",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "flexvolume-dir",
|
||||
MountPath: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeScheduler: {
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/scheduler.conf",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Should ignore files in /etc/ssl/certs
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
CertificatesDir: testCertsDir,
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
Endpoints: []string{"foo"},
|
||||
CAFile: "/etc/certs/etcd/my-etcd-ca.crt",
|
||||
CertFile: "/var/lib/certs/etcd/my-etcd.crt",
|
||||
KeyFile: "/var/lib/certs/etcd/my-etcd.key",
|
||||
},
|
||||
},
|
||||
vol: map[string][]v1.Volume{
|
||||
kubeadmconstants.KubeAPIServer: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: testCertsDir,
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/lib/certs/etcd",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeControllerManager: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: testCertsDir,
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/controller-manager.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "flexvolume-dir",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeScheduler: {
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/scheduler.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volMount: map[string][]v1.VolumeMount{
|
||||
kubeadmconstants.KubeAPIServer: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: testCertsDir,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-0",
|
||||
MountPath: "/etc/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "etcd-certs-1",
|
||||
MountPath: "/var/lib/certs/etcd",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeControllerManager: {
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: testCertsDir,
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/controller-manager.conf",
|
||||
ReadOnly: true,
|
||||
},
|
||||
{
|
||||
Name: "flexvolume-dir",
|
||||
MountPath: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeScheduler: {
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/scheduler.conf",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// set up tmp caCertsPkiVolumePath for testing
|
||||
caCertsPkiVolumePath = fmt.Sprintf("%s/etc/pki", tmpdir)
|
||||
defer func() { caCertsPkiVolumePath = "/etc/pki" }()
|
||||
|
||||
for _, rt := range tests {
|
||||
mounts := getHostPathVolumesForTheControlPlane(rt.cfg)
|
||||
if !reflect.DeepEqual(mounts.volumes, rt.vol) {
|
||||
t.Errorf(
|
||||
"failed getHostPathVolumesForTheControlPlane:\n\texpected: %v\n\t actual: %v",
|
||||
rt.vol,
|
||||
mounts.volumes,
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(mounts.volumeMounts, rt.volMount) {
|
||||
t.Errorf(
|
||||
"failed getHostPathVolumesForTheControlPlane:\n\texpected: %v\n\t actual: %v",
|
||||
rt.volMount,
|
||||
mounts.volumeMounts,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/BUILD
generated
vendored
Normal file
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["local_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["local.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/staticpod:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
74
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/local.go
generated
vendored
Normal file
74
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/local.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
const (
|
||||
etcdVolumeName = "etcd"
|
||||
)
|
||||
|
||||
// CreateLocalEtcdStaticPodManifestFile will write local etcd static pod manifest file.
|
||||
func CreateLocalEtcdStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
// gets etcd StaticPodSpec, actualized for the current MasterConfiguration
|
||||
spec := GetEtcdPodSpec(cfg)
|
||||
|
||||
// writes etcd StaticPod to disk
|
||||
if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("[etcd] Wrote Static Pod manifest for a local etcd instance to %q\n", kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestDir))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEtcdPodSpec returns the etcd static Pod actualized to the context of the current MasterConfiguration
|
||||
// NB. GetEtcdPodSpec methods holds the information about how kubeadm creates etcd static pod mainfests.
|
||||
func GetEtcdPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.Pod {
|
||||
pathType := v1.HostPathDirectoryOrCreate
|
||||
return staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.Etcd,
|
||||
Command: getEtcdCommand(cfg),
|
||||
Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, "", cfg.Etcd.Image),
|
||||
// Mount the etcd datadir path read-write so etcd can store data in a more persistent manner
|
||||
VolumeMounts: []v1.VolumeMount{staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)},
|
||||
LivenessProbe: staticpodutil.ComponentProbe(2379, "/health", v1.URISchemeHTTP),
|
||||
}, []v1.Volume{staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir, &pathType)})
|
||||
}
|
||||
|
||||
// getEtcdCommand builds the right etcd command from the given config object
|
||||
func getEtcdCommand(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||
defaultArguments := map[string]string{
|
||||
"listen-client-urls": "http://127.0.0.1:2379",
|
||||
"advertise-client-urls": "http://127.0.0.1:2379",
|
||||
"data-dir": cfg.Etcd.DataDir,
|
||||
}
|
||||
|
||||
command := []string{"etcd"}
|
||||
command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Etcd.ExtraArgs)...)
|
||||
return command
|
||||
}
|
||||
125
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/local_test.go
generated
vendored
Normal file
125
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd/local_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
)
|
||||
|
||||
func TestGetEtcdPodSpec(t *testing.T) {
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Executes GetEtcdPodSpec
|
||||
spec := GetEtcdPodSpec(cfg)
|
||||
|
||||
// Assert each specs refers to the right pod
|
||||
if spec.Spec.Containers[0].Name != kubeadmconstants.Etcd {
|
||||
t.Errorf("getKubeConfigSpecs spec for etcd contains pod %s, expectes %s", spec.Spec.Containers[0].Name, kubeadmconstants.Etcd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLocalEtcdStaticPodManifestFile(t *testing.T) {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Execute createStaticPodFunction
|
||||
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
|
||||
err := CreateLocalEtcdStaticPodManifestFile(manifestPath, cfg)
|
||||
if err != nil {
|
||||
t.Errorf("Error executing CreateEtcdStaticPodManifestFile: %v", err)
|
||||
}
|
||||
|
||||
// Assert expected files are there
|
||||
testutil.AssertFilesCount(t, manifestPath, 1)
|
||||
testutil.AssertFileExists(t, manifestPath, kubeadmconstants.Etcd+".yaml")
|
||||
}
|
||||
|
||||
func TestGetEtcdCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/var/lib/etcd"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
DataDir: "/var/lib/etcd",
|
||||
ExtraArgs: map[string]string{
|
||||
"listen-client-urls": "http://10.0.1.10:2379",
|
||||
"advertise-client-urls": "http://10.0.1.10:2379",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://10.0.1.10:2379",
|
||||
"--advertise-client-urls=http://10.0.1.10:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/etc/foo"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/etc/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getEtcdCommand(rt.cfg)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getEtcdCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
55
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"kubeconfig.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/cert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["kubeconfig_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
"//cmd/kubeadm/test/certs:go_default_library",
|
||||
"//cmd/kubeadm/test/kubeconfig:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
],
|
||||
)
|
||||
35
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/doc.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright 2016 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 kubeconfig
|
||||
|
||||
/*
|
||||
|
||||
PHASE: KUBECONFIG
|
||||
|
||||
INPUTS:
|
||||
From MasterConfiguration
|
||||
The Master API Server endpoint (AdvertiseAddress + BindPort) is required so the KubeConfig file knows where to find the master
|
||||
The KubernetesDir path is required for knowing where to put the KubeConfig files
|
||||
The PKIPath is required for knowing where all certificates should be stored
|
||||
|
||||
OUTPUTS:
|
||||
Files to KubernetesDir (default /etc/kubernetes):
|
||||
- admin.conf
|
||||
- kubelet.conf
|
||||
- scheduler.conf
|
||||
- controller-manager.conf
|
||||
*/
|
||||
335
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go
generated
vendored
Normal file
335
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go
generated
vendored
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
Copyright 2016 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 kubeconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"crypto/rsa"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
)
|
||||
|
||||
// clientCertAuth struct holds info required to build a client certificate to provide authentication info in a kubeconfig object
|
||||
type clientCertAuth struct {
|
||||
CAKey *rsa.PrivateKey
|
||||
Organizations []string
|
||||
}
|
||||
|
||||
// tokenAuth struct holds info required to use a token to provide authentication info in a kubeconfig object
|
||||
type tokenAuth struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// kubeConfigSpec struct holds info required to build a KubeConfig object
|
||||
type kubeConfigSpec struct {
|
||||
CACert *x509.Certificate
|
||||
APIServer string
|
||||
ClientName string
|
||||
TokenAuth *tokenAuth
|
||||
ClientCertAuth *clientCertAuth
|
||||
}
|
||||
|
||||
// CreateInitKubeConfigFiles will create and write to disk all kubeconfig files necessary in the kubeadm init phase
|
||||
// to establish the control plane, including also the admin kubeconfig file.
|
||||
// If kubeconfig files already exists, they are used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateInitKubeConfigFiles(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(
|
||||
outDir,
|
||||
cfg,
|
||||
kubeadmconstants.AdminKubeConfigFileName,
|
||||
kubeadmconstants.KubeletKubeConfigFileName,
|
||||
kubeadmconstants.ControllerManagerKubeConfigFileName,
|
||||
kubeadmconstants.SchedulerKubeConfigFileName,
|
||||
)
|
||||
}
|
||||
|
||||
// CreateAdminKubeConfigFile create a kubeconfig file for the admin to use and for kubeadm itself.
|
||||
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateAdminKubeConfigFile(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.AdminKubeConfigFileName)
|
||||
}
|
||||
|
||||
// CreateKubeletKubeConfigFile create a kubeconfig file for the Kubelet to use.
|
||||
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateKubeletKubeConfigFile(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.KubeletKubeConfigFileName)
|
||||
}
|
||||
|
||||
// CreateControllerManagerKubeConfigFile create a kubeconfig file for the ControllerManager to use.
|
||||
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateControllerManagerKubeConfigFile(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.ControllerManagerKubeConfigFileName)
|
||||
}
|
||||
|
||||
// CreateSchedulerKubeConfigFile create a create a kubeconfig file for the Scheduler to use.
|
||||
// If the kubeconfig file already exists, it is used only if evaluated equal; otherwise an error is returned.
|
||||
func CreateSchedulerKubeConfigFile(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(outDir, cfg, kubeadmconstants.SchedulerKubeConfigFileName)
|
||||
}
|
||||
|
||||
// createKubeConfigFiles creates all the requested kubeconfig files.
|
||||
// If kubeconfig files already exists, they are used only if evaluated equal; otherwise an error is returned.
|
||||
func createKubeConfigFiles(outDir string, cfg *kubeadmapi.MasterConfiguration, kubeConfigFileNames ...string) error {
|
||||
|
||||
// gets the KubeConfigSpecs, actualized for the current MasterConfiguration
|
||||
specs, err := getKubeConfigSpecs(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kubeConfigFileName := range kubeConfigFileNames {
|
||||
// retrives the KubeConfigSpec for given kubeConfigFileName
|
||||
spec, exists := specs[kubeConfigFileName]
|
||||
if !exists {
|
||||
return fmt.Errorf("couldn't retrive KubeConfigSpec for %s", kubeConfigFileName)
|
||||
}
|
||||
|
||||
// builds the KubeConfig object
|
||||
config, err := buildKubeConfigFromSpec(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// writes the KubeConfig to disk if it not exists
|
||||
if err = createKubeConfigFileIfNotExists(outDir, kubeConfigFileName, config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getKubeConfigSpecs returns all KubeConfigSpecs actualized to the context of the current MasterConfiguration
|
||||
// NB. this methods holds the information about how kubeadm creates kubeconfig files.
|
||||
func getKubeConfigSpecs(cfg *kubeadmapi.MasterConfiguration) (map[string]*kubeConfigSpec, error) {
|
||||
|
||||
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err)
|
||||
}
|
||||
|
||||
masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kubeConfigSpec = map[string]*kubeConfigSpec{
|
||||
kubeadmconstants.AdminKubeConfigFileName: {
|
||||
CACert: caCert,
|
||||
APIServer: masterEndpoint,
|
||||
ClientName: "kubernetes-admin",
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
Organizations: []string{kubeadmconstants.MastersGroup},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.KubeletKubeConfigFileName: {
|
||||
CACert: caCert,
|
||||
APIServer: masterEndpoint,
|
||||
ClientName: fmt.Sprintf("system:node:%s", cfg.NodeName),
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
Organizations: []string{kubeadmconstants.NodesGroup},
|
||||
},
|
||||
},
|
||||
kubeadmconstants.ControllerManagerKubeConfigFileName: {
|
||||
CACert: caCert,
|
||||
APIServer: masterEndpoint,
|
||||
ClientName: kubeadmconstants.ControllerManagerUser,
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
},
|
||||
},
|
||||
kubeadmconstants.SchedulerKubeConfigFileName: {
|
||||
CACert: caCert,
|
||||
APIServer: masterEndpoint,
|
||||
ClientName: kubeadmconstants.SchedulerUser,
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return kubeConfigSpec, nil
|
||||
}
|
||||
|
||||
// buildKubeConfigFromSpec creates a kubeconfig object for the given kubeConfigSpec
|
||||
func buildKubeConfigFromSpec(spec *kubeConfigSpec) (*clientcmdapi.Config, error) {
|
||||
|
||||
// If this kubeconfig should use token
|
||||
if spec.TokenAuth != nil {
|
||||
// create a kubeconfig with a token
|
||||
return kubeconfigutil.CreateWithToken(
|
||||
spec.APIServer,
|
||||
"kubernetes",
|
||||
spec.ClientName,
|
||||
certutil.EncodeCertPEM(spec.CACert),
|
||||
spec.TokenAuth.Token,
|
||||
), nil
|
||||
}
|
||||
|
||||
// otherwise, create a client certs
|
||||
clientCertConfig := certutil.Config{
|
||||
CommonName: spec.ClientName,
|
||||
Organization: spec.ClientCertAuth.Organizations,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, clientCertConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failure while creating %s client certificate: %v", spec.ClientName, err)
|
||||
}
|
||||
|
||||
// create a kubeconfig with the client certs
|
||||
return kubeconfigutil.CreateWithCerts(
|
||||
spec.APIServer,
|
||||
"kubernetes",
|
||||
spec.ClientName,
|
||||
certutil.EncodeCertPEM(spec.CACert),
|
||||
certutil.EncodePrivateKeyPEM(clientKey),
|
||||
certutil.EncodeCertPEM(clientCert),
|
||||
), nil
|
||||
}
|
||||
|
||||
// createKubeConfigFileIfNotExists saves the KubeConfig object into a file if there isn't any file at the given path.
|
||||
// If there already is a KubeConfig file at the given path; kubeadm tries to load it and check if the values in the
|
||||
// existing and the expected config equals. If they do; kubeadm will just skip writing the file as it's up-to-date,
|
||||
// but if a file exists but has old content or isn't a kubeconfig file, this function returns an error.
|
||||
func createKubeConfigFileIfNotExists(outDir, filename string, config *clientcmdapi.Config) error {
|
||||
kubeConfigFilePath := filepath.Join(outDir, filename)
|
||||
|
||||
// Check if the file exist, and if it doesn't, just write it to disk
|
||||
if _, err := os.Stat(kubeConfigFilePath); os.IsNotExist(err) {
|
||||
err = kubeconfigutil.WriteToDisk(kubeConfigFilePath, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save kubeconfig file %s on disk: %v", kubeConfigFilePath, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[kubeconfig] Wrote KubeConfig file to disk: %q\n", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// The kubeconfig already exists, let's check if it has got the same CA and server URL
|
||||
currentConfig, err := clientcmd.LoadFromFile(kubeConfigFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load kubeconfig file %s that already exists on disk: %v", kubeConfigFilePath, err)
|
||||
}
|
||||
|
||||
expectedCtx := config.CurrentContext
|
||||
expectedCluster := config.Contexts[expectedCtx].Cluster
|
||||
currentCtx := currentConfig.CurrentContext
|
||||
currentCluster := currentConfig.Contexts[currentCtx].Cluster
|
||||
|
||||
// If the current CA cert on disk doesn't match the expected CA cert, error out because we have a file, but it's stale
|
||||
if !bytes.Equal(currentConfig.Clusters[currentCluster].CertificateAuthorityData, config.Clusters[expectedCluster].CertificateAuthorityData) {
|
||||
return fmt.Errorf("a kubeconfig file %q exists already but has got the wrong CA cert", kubeConfigFilePath)
|
||||
}
|
||||
// If the current API Server location on disk doesn't match the expected API server, error out because we have a file, but it's stale
|
||||
if currentConfig.Clusters[currentCluster].Server != config.Clusters[expectedCluster].Server {
|
||||
return fmt.Errorf("a kubeconfig file %q exists already but has got the wrong API Server URL", kubeConfigFilePath)
|
||||
}
|
||||
|
||||
// kubeadm doesn't validate the existing kubeconfig file more than this (kubeadm trusts the client certs to be valid)
|
||||
// Basically, if we find a kubeconfig file with the same path; the same CA cert and the same server URL;
|
||||
// kubeadm thinks those files are equal and doesn't bother writing a new file
|
||||
fmt.Printf("[kubeconfig] Using existing up-to-date KubeConfig file: %q\n", filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteKubeConfigWithClientCert writes a kubeconfig file - with a client certificate as authentication info - to the given writer.
|
||||
func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.MasterConfiguration, clientName string) error {
|
||||
|
||||
// creates the KubeConfigSpecs, actualized for the current MasterConfiguration
|
||||
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err)
|
||||
}
|
||||
|
||||
masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := &kubeConfigSpec{
|
||||
ClientName: clientName,
|
||||
APIServer: masterEndpoint,
|
||||
CACert: caCert,
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
},
|
||||
}
|
||||
|
||||
return writeKubeConfigFromSpec(out, spec)
|
||||
}
|
||||
|
||||
// WriteKubeConfigWithToken writes a kubeconfig file - with a token as client authentication info - to the given writer.
|
||||
func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.MasterConfiguration, clientName, token string) error {
|
||||
|
||||
// creates the KubeConfigSpecs, actualized for the current MasterConfiguration
|
||||
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create a kubeconfig; the CA files couldn't be loaded: %v", err)
|
||||
}
|
||||
|
||||
masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec := &kubeConfigSpec{
|
||||
ClientName: clientName,
|
||||
APIServer: masterEndpoint,
|
||||
CACert: caCert,
|
||||
TokenAuth: &tokenAuth{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
|
||||
return writeKubeConfigFromSpec(out, spec)
|
||||
}
|
||||
|
||||
// writeKubeConfigFromSpec creates a kubeconfig object from a kubeConfigSpec and writes it to the given writer.
|
||||
func writeKubeConfigFromSpec(out io.Writer, spec *kubeConfigSpec) error {
|
||||
|
||||
// builds the KubeConfig object
|
||||
config, err := buildKubeConfigFromSpec(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// writes the KubeConfig to disk if it not exists
|
||||
configBytes, err := clientcmd.Write(*config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure while serializing admin kubeconfig: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, string(configBytes))
|
||||
return nil
|
||||
}
|
||||
440
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go
generated
vendored
Normal file
440
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
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 kubeconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
|
||||
kubeconfigtestutil "k8s.io/kubernetes/cmd/kubeadm/test/kubeconfig"
|
||||
)
|
||||
|
||||
func TestGetKubeConfigSpecsFailsIfCADoesntExists(t *testing.T) {
|
||||
// Create temp folder for the test case (without a CA)
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration pointing to the pkidir folder
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
CertificatesDir: tmpdir,
|
||||
}
|
||||
|
||||
// Executes getKubeConfigSpecs
|
||||
if _, err := getKubeConfigSpecs(cfg); err == nil {
|
||||
t.Error("getKubeConfigSpecs didnt failed when expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKubeConfigSpecs(t *testing.T) {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Adds a pki folder with a ca certs to the temp folder
|
||||
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)
|
||||
|
||||
// Creates a Master Configuration pointing to the pkidir folder
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
|
||||
CertificatesDir: pkidir,
|
||||
NodeName: "valid-node-name",
|
||||
}
|
||||
|
||||
// Executes getKubeConfigSpecs
|
||||
specs, err := getKubeConfigSpecs(cfg)
|
||||
if err != nil {
|
||||
t.Fatal("getKubeConfigSpecs failed!")
|
||||
}
|
||||
|
||||
var assertions = []struct {
|
||||
kubeConfigFile string
|
||||
clientName string
|
||||
organizations []string
|
||||
}{
|
||||
{
|
||||
kubeConfigFile: kubeadmconstants.AdminKubeConfigFileName,
|
||||
clientName: "kubernetes-admin",
|
||||
organizations: []string{kubeadmconstants.MastersGroup},
|
||||
},
|
||||
{
|
||||
kubeConfigFile: kubeadmconstants.KubeletKubeConfigFileName,
|
||||
clientName: fmt.Sprintf("system:node:%s", cfg.NodeName),
|
||||
organizations: []string{kubeadmconstants.NodesGroup},
|
||||
},
|
||||
{
|
||||
kubeConfigFile: kubeadmconstants.ControllerManagerKubeConfigFileName,
|
||||
clientName: kubeadmconstants.ControllerManagerUser,
|
||||
},
|
||||
{
|
||||
kubeConfigFile: kubeadmconstants.SchedulerKubeConfigFileName,
|
||||
clientName: kubeadmconstants.SchedulerUser,
|
||||
},
|
||||
}
|
||||
|
||||
for _, assertion := range assertions {
|
||||
|
||||
// assert the spec for the kubeConfigFile exists
|
||||
if spec, ok := specs[assertion.kubeConfigFile]; ok {
|
||||
|
||||
// Assert clientName
|
||||
if spec.ClientName != assertion.clientName {
|
||||
t.Errorf("getKubeConfigSpecs for %s clientName is %s, expected %s", assertion.kubeConfigFile, spec.ClientName, assertion.clientName)
|
||||
}
|
||||
|
||||
// Assert Organizations
|
||||
if spec.ClientCertAuth == nil || !reflect.DeepEqual(spec.ClientCertAuth.Organizations, assertion.organizations) {
|
||||
t.Errorf("getKubeConfigSpecs for %s Organizations is %v, expected %v", assertion.kubeConfigFile, spec.ClientCertAuth.Organizations, assertion.organizations)
|
||||
}
|
||||
|
||||
// Asserts MasterConfiguration values injected into spec
|
||||
masterEndpoint, err := kubeadmutil.GetMasterEndpoint(cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if spec.APIServer != masterEndpoint {
|
||||
t.Errorf("getKubeConfigSpecs didn't injected cfg.APIServer endpoint into spec for %s", assertion.kubeConfigFile)
|
||||
}
|
||||
|
||||
// Asserts CA certs and CA keys loaded into specs
|
||||
if spec.CACert == nil {
|
||||
t.Errorf("getKubeConfigSpecs didn't loaded CACert into spec for %s!", assertion.kubeConfigFile)
|
||||
}
|
||||
if spec.ClientCertAuth == nil || spec.ClientCertAuth.CAKey == nil {
|
||||
t.Errorf("getKubeConfigSpecs didn't loaded CAKey into spec for %s!", assertion.kubeConfigFile)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("getKubeConfigSpecs didn't create spec for %s ", assertion.kubeConfigFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildKubeConfigFromSpecWithClientAuth(t *testing.T) {
|
||||
// Creates a CA
|
||||
caCert, caKey := certstestutil.SetupCertificateAuthorithy(t)
|
||||
|
||||
// Executes buildKubeConfigFromSpec passing a KubeConfigSpec wiht a ClientAuth
|
||||
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "myClientName", "myOrg1", "myOrg2")
|
||||
|
||||
// Asserts spec data are propagated to the kubeconfig
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myClientName", "myOrg1", "myOrg2")
|
||||
}
|
||||
|
||||
func TestBuildKubeConfigFromSpecWithTokenAuth(t *testing.T) {
|
||||
// Creates a CA
|
||||
caCert, _ := certstestutil.SetupCertificateAuthorithy(t)
|
||||
|
||||
// Executes buildKubeConfigFromSpec passing a KubeConfigSpec wiht a Token
|
||||
config := setupdKubeConfigWithTokenAuth(t, caCert, "https://1.2.3.4:1234", "myClientName", "123456")
|
||||
|
||||
// Asserts spec data are propagated to the kubeconfig
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithToken(t, config, "myClientName", "123456")
|
||||
}
|
||||
|
||||
func TestCreateKubeConfigFileIfNotExists(t *testing.T) {
|
||||
|
||||
// Creates a CAs
|
||||
caCert, caKey := certstestutil.SetupCertificateAuthorithy(t)
|
||||
anotherCaCert, anotherCaKey := certstestutil.SetupCertificateAuthorithy(t)
|
||||
|
||||
// build kubeconfigs (to be used to test kubeconfigs equality/not equality)
|
||||
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "myOrg1", "myOrg2")
|
||||
configWithAnotherClusterCa := setupdKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, "https://1.2.3.4:1234", "myOrg1", "myOrg2")
|
||||
configWithAnotherClusterAddress := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://3.4.5.6:3456", "myOrg1", "myOrg2")
|
||||
|
||||
var tests = []struct {
|
||||
existingKubeConfig *clientcmdapi.Config
|
||||
kubeConfig *clientcmdapi.Config
|
||||
expectedError bool
|
||||
}{
|
||||
{ // if there is no existing KubeConfig, creates the kubeconfig
|
||||
kubeConfig: config,
|
||||
},
|
||||
{ // if KubeConfig is equal to the existingKubeConfig - refers to the same cluster -, use the existing (Test idempotency)
|
||||
existingKubeConfig: config,
|
||||
kubeConfig: config,
|
||||
},
|
||||
{ // if KubeConfig is not equal to the existingKubeConfig - refers to the another cluster (a cluster with another Ca) -, raise error
|
||||
existingKubeConfig: config,
|
||||
kubeConfig: configWithAnotherClusterCa,
|
||||
expectedError: true,
|
||||
},
|
||||
{ // if KubeConfig is not equal to the existingKubeConfig - refers to the another cluster (a cluster with another address) -, raise error
|
||||
existingKubeConfig: config,
|
||||
kubeConfig: configWithAnotherClusterAddress,
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Writes the existing kubeconfig file to disk
|
||||
if test.existingKubeConfig != nil {
|
||||
if err := createKubeConfigFileIfNotExists(tmpdir, "test.conf", test.existingKubeConfig); err != nil {
|
||||
t.Errorf("createKubeConfigFileIfNotExists failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the KubeConfig file to disk
|
||||
err := createKubeConfigFileIfNotExists(tmpdir, "test.conf", test.kubeConfig)
|
||||
if test.expectedError && err == nil {
|
||||
t.Errorf("createKubeConfigFileIfNotExists didn't failed when expected to fail")
|
||||
}
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("createKubeConfigFileIfNotExists failed")
|
||||
}
|
||||
|
||||
// Assert creted files is there
|
||||
testutil.AssertFileExists(t, tmpdir, "test.conf")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateKubeconfigFilesAndWrappers(t *testing.T) {
|
||||
var tests = []struct {
|
||||
createKubeConfigFunction func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
|
||||
expectedFiles []string
|
||||
expectedError bool
|
||||
}{
|
||||
{ // Test createKubeConfigFiles fails for unknown kubeconfig is requested
|
||||
createKubeConfigFunction: func(outDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createKubeConfigFiles(outDir, cfg, "unknown.conf")
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
{ // Test CreateInitKubeConfigFiles (wrapper to createKubeConfigFile)
|
||||
createKubeConfigFunction: CreateInitKubeConfigFiles,
|
||||
expectedFiles: []string{
|
||||
kubeadmconstants.AdminKubeConfigFileName,
|
||||
kubeadmconstants.KubeletKubeConfigFileName,
|
||||
kubeadmconstants.ControllerManagerKubeConfigFileName,
|
||||
kubeadmconstants.SchedulerKubeConfigFileName,
|
||||
},
|
||||
},
|
||||
{ // Test CreateAdminKubeConfigFile (wrapper to createKubeConfigFile)
|
||||
createKubeConfigFunction: CreateAdminKubeConfigFile,
|
||||
expectedFiles: []string{kubeadmconstants.AdminKubeConfigFileName},
|
||||
},
|
||||
{ // Test CreateKubeletKubeConfigFile (wrapper to createKubeConfigFile)
|
||||
createKubeConfigFunction: CreateKubeletKubeConfigFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeletKubeConfigFileName},
|
||||
},
|
||||
{ // Test CreateControllerManagerKubeConfigFile (wrapper to createKubeConfigFile)
|
||||
createKubeConfigFunction: CreateControllerManagerKubeConfigFile,
|
||||
expectedFiles: []string{kubeadmconstants.ControllerManagerKubeConfigFileName},
|
||||
},
|
||||
{ // Test createKubeConfigFile (wrapper to createKubeConfigFile)
|
||||
createKubeConfigFunction: CreateSchedulerKubeConfigFile,
|
||||
expectedFiles: []string{kubeadmconstants.SchedulerKubeConfigFileName},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Adds a pki folder with a ca certs to the temp folder
|
||||
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)
|
||||
|
||||
// Creates a Master Configuration pointing to the pkidir folder
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
|
||||
CertificatesDir: pkidir,
|
||||
}
|
||||
|
||||
// Execs the createKubeConfigFunction
|
||||
err := test.createKubeConfigFunction(tmpdir, cfg)
|
||||
if test.expectedError && err == nil {
|
||||
t.Errorf("createKubeConfigFunction didn't failed when expected to fail")
|
||||
continue
|
||||
}
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf("createKubeConfigFunction failed")
|
||||
continue
|
||||
}
|
||||
|
||||
// Assert expected files are there
|
||||
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteKubeConfigFailsIfCADoesntExists(t *testing.T) {
|
||||
|
||||
// Temporary folders for the test case (without a CA)
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration pointing to the tmpdir folder
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
CertificatesDir: tmpdir,
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
writeKubeConfigFunction func(out io.Writer) error
|
||||
}{
|
||||
{ // Test WriteKubeConfigWithClientCert
|
||||
writeKubeConfigFunction: func(out io.Writer) error {
|
||||
return WriteKubeConfigWithClientCert(out, cfg, "myUser")
|
||||
},
|
||||
},
|
||||
{ // Test WriteKubeConfigWithToken
|
||||
writeKubeConfigFunction: func(out io.Writer) error {
|
||||
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// executes writeKubeConfigFunction
|
||||
if err := test.writeKubeConfigFunction(buf); err == nil {
|
||||
t.Error("writeKubeConfigFunction didnt failed when expected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteKubeConfig(t *testing.T) {
|
||||
|
||||
// Temporary folders for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Adds a pki folder with a ca cert to the temp folder
|
||||
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)
|
||||
|
||||
// Retrives ca cert for assertions
|
||||
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkidir, kubeadmconstants.CACertAndKeyBaseName)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't retrive ca cert: %v", err)
|
||||
}
|
||||
|
||||
// Creates a Master Configuration pointing to the pkidir folder
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
|
||||
CertificatesDir: pkidir,
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
writeKubeConfigFunction func(out io.Writer) error
|
||||
withClientCert bool
|
||||
withToken bool
|
||||
}{
|
||||
{ // Test WriteKubeConfigWithClientCert
|
||||
writeKubeConfigFunction: func(out io.Writer) error {
|
||||
return WriteKubeConfigWithClientCert(out, cfg, "myUser")
|
||||
},
|
||||
withClientCert: true,
|
||||
},
|
||||
{ // Test WriteKubeConfigWithToken
|
||||
writeKubeConfigFunction: func(out io.Writer) error {
|
||||
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345")
|
||||
},
|
||||
withToken: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// executes writeKubeConfigFunction
|
||||
if err := test.writeKubeConfigFunction(buf); err != nil {
|
||||
t.Error("writeKubeConfigFunction failed")
|
||||
continue
|
||||
}
|
||||
|
||||
// reads kubeconfig written to stdout
|
||||
config, err := clientcmd.Load(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't read kubeconfig file from buffer: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// checks that CLI flags are properly propagated
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
|
||||
|
||||
if test.withClientCert {
|
||||
// checks that kubeconfig files have expected client cert
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myUser")
|
||||
}
|
||||
|
||||
if test.withToken {
|
||||
// checks that kubeconfig files have expected token
|
||||
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithToken(t, config, "myUser", "12345")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setupdKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With ClientAuth
|
||||
func setupdKubeConfigWithClientAuth(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey, APIServer, clientName string, organizations ...string) *clientcmdapi.Config {
|
||||
spec := &kubeConfigSpec{
|
||||
CACert: caCert,
|
||||
APIServer: APIServer,
|
||||
ClientName: clientName,
|
||||
ClientCertAuth: &clientCertAuth{
|
||||
CAKey: caKey,
|
||||
Organizations: organizations,
|
||||
},
|
||||
}
|
||||
|
||||
config, err := buildKubeConfigFromSpec(spec)
|
||||
if err != nil {
|
||||
t.Fatal("buildKubeConfigFromSpec failed!")
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// setupdKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With Token
|
||||
func setupdKubeConfigWithTokenAuth(t *testing.T, caCert *x509.Certificate, APIServer, clientName, token string) *clientcmdapi.Config {
|
||||
spec := &kubeConfigSpec{
|
||||
CACert: caCert,
|
||||
APIServer: APIServer,
|
||||
ClientName: clientName,
|
||||
TokenAuth: &tokenAuth{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
|
||||
config, err := buildKubeConfigFromSpec(spec)
|
||||
if err != nil {
|
||||
t.Fatal("buildKubeConfigFromSpec failed!")
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
51
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["markmaster_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//pkg/util/node:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["markmaster.go"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
98
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/markmaster.go
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/markmaster.go
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 markmaster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
)
|
||||
|
||||
// MarkMaster taints the master and sets the master label
|
||||
func MarkMaster(client clientset.Interface, masterName string) error {
|
||||
|
||||
fmt.Printf("[markmaster] Will mark node %s as master by adding a label and a taint\n", masterName)
|
||||
|
||||
// Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned.
|
||||
return wait.Poll(kubeadmconstants.APICallRetryInterval, kubeadmconstants.MarkMasterTimeout, func() (bool, error) {
|
||||
// First get the node object
|
||||
n, err := client.CoreV1().Nodes().Get(masterName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The node may appear to have no labels at first,
|
||||
// so we wait for it to get hostname label.
|
||||
if _, found := n.ObjectMeta.Labels[kubeletapis.LabelHostname]; !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
oldData, err := json.Marshal(n)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// The master node should be tainted and labelled accordingly
|
||||
markMasterNode(n)
|
||||
|
||||
newData, err := json.Marshal(n)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := client.CoreV1().Nodes().Patch(n.Name, types.StrategicMergePatchType, patchBytes); err != nil {
|
||||
if apierrs.IsConflict(err) {
|
||||
fmt.Println("[markmaster] Temporarily unable to update master node metadata due to conflict (will retry)")
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
fmt.Printf("[markmaster] Master %s tainted and labelled with key/value: %s=%q\n", masterName, kubeadmconstants.LabelNodeRoleMaster, "")
|
||||
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func markMasterNode(n *v1.Node) {
|
||||
n.ObjectMeta.Labels[kubeadmconstants.LabelNodeRoleMaster] = ""
|
||||
addTaintIfNotExists(n, kubeadmconstants.MasterTaint)
|
||||
}
|
||||
|
||||
func addTaintIfNotExists(n *v1.Node, t v1.Taint) {
|
||||
for _, taint := range n.Spec.Taints {
|
||||
if taint == t {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n.Spec.Taints = append(n.Spec.Taints, t)
|
||||
}
|
||||
143
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/markmaster_test.go
generated
vendored
Normal file
143
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster/markmaster_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
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 markmaster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
"k8s.io/kubernetes/pkg/util/node"
|
||||
)
|
||||
|
||||
func TestMarkMaster(t *testing.T) {
|
||||
// Note: this test takes advantage of the deterministic marshalling of
|
||||
// JSON provided by strategicpatch so that "expectedPatch" can use a
|
||||
// string equality test instead of a logical JSON equality test. That
|
||||
// will need to change if strategicpatch's behavior changes in the
|
||||
// future.
|
||||
tests := []struct {
|
||||
name string
|
||||
existingLabel string
|
||||
existingTaint *v1.Taint
|
||||
expectedPatch string
|
||||
}{
|
||||
{
|
||||
"master label and taint missing",
|
||||
"",
|
||||
nil,
|
||||
"{\"metadata\":{\"labels\":{\"node-role.kubernetes.io/master\":\"\"}},\"spec\":{\"taints\":[{\"effect\":\"NoSchedule\",\"key\":\"node-role.kubernetes.io/master\",\"timeAdded\":null}]}}",
|
||||
},
|
||||
{
|
||||
"master label missing",
|
||||
"",
|
||||
&kubeadmconstants.MasterTaint,
|
||||
"{\"metadata\":{\"labels\":{\"node-role.kubernetes.io/master\":\"\"}}}",
|
||||
},
|
||||
{
|
||||
"master taint missing",
|
||||
kubeadmconstants.LabelNodeRoleMaster,
|
||||
nil,
|
||||
"{\"spec\":{\"taints\":[{\"effect\":\"NoSchedule\",\"key\":\"node-role.kubernetes.io/master\",\"timeAdded\":null}]}}",
|
||||
},
|
||||
{
|
||||
"nothing missing",
|
||||
kubeadmconstants.LabelNodeRoleMaster,
|
||||
&kubeadmconstants.MasterTaint,
|
||||
"{}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
hostname := node.GetHostname("")
|
||||
masterNode := &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: hostname,
|
||||
Labels: map[string]string{
|
||||
kubeletapis.LabelHostname: hostname,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if tc.existingLabel != "" {
|
||||
masterNode.ObjectMeta.Labels[tc.existingLabel] = ""
|
||||
}
|
||||
|
||||
if tc.existingTaint != nil {
|
||||
masterNode.Spec.Taints = append(masterNode.Spec.Taints, *tc.existingTaint)
|
||||
}
|
||||
|
||||
jsonNode, err := json.Marshal(masterNode)
|
||||
if err != nil {
|
||||
t.Fatalf("MarkMaster(%s): unexpected encoding error: %v", tc.name, err)
|
||||
}
|
||||
|
||||
var patchRequest string
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if req.URL.Path != "/api/v1/nodes/"+hostname {
|
||||
t.Errorf("MarkMaster(%s): request for unexpected HTTP resource: %v", tc.name, req.URL.Path)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
case "PATCH":
|
||||
patchRequest = toString(req.Body)
|
||||
default:
|
||||
t.Errorf("MarkMaster(%s): request for unexpected HTTP verb: %v", tc.name, req.Method)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonNode)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
cs, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
|
||||
if err != nil {
|
||||
t.Fatalf("MarkMaster(%s): unexpected error building clientset: %v", tc.name, err)
|
||||
}
|
||||
|
||||
err = MarkMaster(cs, hostname)
|
||||
if err != nil {
|
||||
t.Errorf("MarkMaster(%s) returned unexpected error: %v", tc.name, err)
|
||||
}
|
||||
|
||||
if tc.expectedPatch != patchRequest {
|
||||
t.Errorf("MarkMaster(%s) wanted patch %v, got %v", tc.name, tc.expectedPatch, patchRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toString(r io.Reader) string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r)
|
||||
return buf.String()
|
||||
}
|
||||
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/BUILD
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"podspec_mutation_test.go",
|
||||
"selfhosting_test.go",
|
||||
"selfhosting_volumes_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"podspec_mutation.go",
|
||||
"selfhosting.go",
|
||||
"selfhosting_volumes.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/features:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
169
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go
generated
vendored
Normal file
169
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/podspec_mutation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// selfHostedKubeConfigDir sets the directory where kubeconfig files for the scheduler and controller-manager should be mounted
|
||||
// Due to how the projected volume mount works (can only be a full directory, not mount individual files), we must change this from
|
||||
// the default as mounts cannot be nested (/etc/kubernetes would override /etc/kubernetes/pki)
|
||||
selfHostedKubeConfigDir = "/etc/kubernetes/kubeconfig"
|
||||
)
|
||||
|
||||
// PodSpecMutatorFunc is a function capable of mutating a PodSpec
|
||||
type PodSpecMutatorFunc func(*v1.PodSpec)
|
||||
|
||||
// GetDefaultMutators gets the mutator functions that alwasy should be used
|
||||
func GetDefaultMutators() map[string][]PodSpecMutatorFunc {
|
||||
return map[string][]PodSpecMutatorFunc{
|
||||
kubeadmconstants.KubeAPIServer: {
|
||||
addNodeSelectorToPodSpec,
|
||||
setMasterTolerationOnPodSpec,
|
||||
setRightDNSPolicyOnPodSpec,
|
||||
},
|
||||
kubeadmconstants.KubeControllerManager: {
|
||||
addNodeSelectorToPodSpec,
|
||||
setMasterTolerationOnPodSpec,
|
||||
setRightDNSPolicyOnPodSpec,
|
||||
},
|
||||
kubeadmconstants.KubeScheduler: {
|
||||
addNodeSelectorToPodSpec,
|
||||
setMasterTolerationOnPodSpec,
|
||||
setRightDNSPolicyOnPodSpec,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetMutatorsFromFeatureGates returns all mutators needed based on the feature gates passed
|
||||
func GetMutatorsFromFeatureGates(featureGates map[string]bool) map[string][]PodSpecMutatorFunc {
|
||||
// Here the map of different mutators to use for the control plane's podspec is stored
|
||||
mutators := GetDefaultMutators()
|
||||
|
||||
// Some extra work to be done if we should store the control plane certificates in Secrets
|
||||
if features.Enabled(featureGates, features.StoreCertsInSecrets) {
|
||||
|
||||
// Add the store-certs-in-secrets-specific mutators here so that the self-hosted component starts using them
|
||||
mutators[kubeadmconstants.KubeAPIServer] = append(mutators[kubeadmconstants.KubeAPIServer], setSelfHostedVolumesForAPIServer)
|
||||
mutators[kubeadmconstants.KubeControllerManager] = append(mutators[kubeadmconstants.KubeControllerManager], setSelfHostedVolumesForControllerManager)
|
||||
mutators[kubeadmconstants.KubeScheduler] = append(mutators[kubeadmconstants.KubeScheduler], setSelfHostedVolumesForScheduler)
|
||||
}
|
||||
return mutators
|
||||
}
|
||||
|
||||
// mutatePodSpec makes a Static Pod-hosted PodSpec suitable for self-hosting
|
||||
func mutatePodSpec(mutators map[string][]PodSpecMutatorFunc, name string, podSpec *v1.PodSpec) {
|
||||
// Get the mutator functions for the component in question, then loop through and execute them
|
||||
mutatorsForComponent := mutators[name]
|
||||
for _, mutateFunc := range mutatorsForComponent {
|
||||
mutateFunc(podSpec)
|
||||
}
|
||||
}
|
||||
|
||||
// addNodeSelectorToPodSpec makes Pod require to be scheduled on a node marked with the master label
|
||||
func addNodeSelectorToPodSpec(podSpec *v1.PodSpec) {
|
||||
if podSpec.NodeSelector == nil {
|
||||
podSpec.NodeSelector = map[string]string{kubeadmconstants.LabelNodeRoleMaster: ""}
|
||||
return
|
||||
}
|
||||
|
||||
podSpec.NodeSelector[kubeadmconstants.LabelNodeRoleMaster] = ""
|
||||
}
|
||||
|
||||
// setMasterTolerationOnPodSpec makes the Pod tolerate the master taint
|
||||
func setMasterTolerationOnPodSpec(podSpec *v1.PodSpec) {
|
||||
if podSpec.Tolerations == nil {
|
||||
podSpec.Tolerations = []v1.Toleration{kubeadmconstants.MasterToleration}
|
||||
return
|
||||
}
|
||||
|
||||
podSpec.Tolerations = append(podSpec.Tolerations, kubeadmconstants.MasterToleration)
|
||||
}
|
||||
|
||||
// setRightDNSPolicyOnPodSpec makes sure the self-hosted components can look up things via kube-dns if necessary
|
||||
func setRightDNSPolicyOnPodSpec(podSpec *v1.PodSpec) {
|
||||
podSpec.DNSPolicy = v1.DNSClusterFirstWithHostNet
|
||||
}
|
||||
|
||||
// setSelfHostedVolumesForAPIServer makes sure the self-hosted api server has the right volume source coming from a self-hosted cluster
|
||||
func setSelfHostedVolumesForAPIServer(podSpec *v1.PodSpec) {
|
||||
for i, v := range podSpec.Volumes {
|
||||
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
|
||||
if v.Name == kubeadmconstants.KubeCertificatesVolumeName {
|
||||
podSpec.Volumes[i].VolumeSource = apiServerCertificatesVolumeSource()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setSelfHostedVolumesForControllerManager makes sure the self-hosted controller manager has the right volume source coming from a self-hosted cluster
|
||||
func setSelfHostedVolumesForControllerManager(podSpec *v1.PodSpec) {
|
||||
for i, v := range podSpec.Volumes {
|
||||
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
|
||||
if v.Name == kubeadmconstants.KubeCertificatesVolumeName {
|
||||
podSpec.Volumes[i].VolumeSource = controllerManagerCertificatesVolumeSource()
|
||||
} else if v.Name == kubeadmconstants.KubeConfigVolumeName {
|
||||
podSpec.Volumes[i].VolumeSource = kubeConfigVolumeSource(kubeadmconstants.ControllerManagerKubeConfigFileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Change directory for the kubeconfig directory to selfHostedKubeConfigDir
|
||||
for i, vm := range podSpec.Containers[0].VolumeMounts {
|
||||
if vm.Name == kubeadmconstants.KubeConfigVolumeName {
|
||||
podSpec.Containers[0].VolumeMounts[i].MountPath = selfHostedKubeConfigDir
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite the --kubeconfig path as the volume mount path may not overlap with certs dir, which it does by default (/etc/kubernetes and /etc/kubernetes/pki)
|
||||
// This is not a problem with hostPath mounts as hostPath supports mounting one file only, instead of always a full directory. Secrets and Projected Volumes
|
||||
// don't support that.
|
||||
podSpec.Containers[0].Command = kubeadmutil.ReplaceArgument(podSpec.Containers[0].Command, func(argMap map[string]string) map[string]string {
|
||||
argMap["kubeconfig"] = filepath.Join(selfHostedKubeConfigDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
|
||||
return argMap
|
||||
})
|
||||
}
|
||||
|
||||
// setSelfHostedVolumesForScheduler makes sure the self-hosted scheduler has the right volume source coming from a self-hosted cluster
|
||||
func setSelfHostedVolumesForScheduler(podSpec *v1.PodSpec) {
|
||||
for i, v := range podSpec.Volumes {
|
||||
// If the volume name matches the expected one; switch the volume source from hostPath to cluster-hosted
|
||||
if v.Name == kubeadmconstants.KubeConfigVolumeName {
|
||||
podSpec.Volumes[i].VolumeSource = kubeConfigVolumeSource(kubeadmconstants.SchedulerKubeConfigFileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Change directory for the kubeconfig directory to selfHostedKubeConfigDir
|
||||
for i, vm := range podSpec.Containers[0].VolumeMounts {
|
||||
if vm.Name == kubeadmconstants.KubeConfigVolumeName {
|
||||
podSpec.Containers[0].VolumeMounts[i].MountPath = selfHostedKubeConfigDir
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite the --kubeconfig path as the volume mount path may not overlap with certs dir, which it does by default (/etc/kubernetes and /etc/kubernetes/pki)
|
||||
// This is not a problem with hostPath mounts as hostPath supports mounting one file only, instead of always a full directory. Secrets and Projected Volumes
|
||||
// don't support that.
|
||||
podSpec.Containers[0].Command = kubeadmutil.ReplaceArgument(podSpec.Containers[0].Command, func(argMap map[string]string) map[string]string {
|
||||
argMap["kubeconfig"] = filepath.Join(selfHostedKubeConfigDir, kubeadmconstants.SchedulerKubeConfigFileName)
|
||||
return argMap
|
||||
})
|
||||
}
|
||||
468
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go
generated
vendored
Normal file
468
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/podspec_mutation_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestMutatePodSpec(t *testing.T) {
|
||||
var tests = []struct {
|
||||
component string
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
component: kubeadmconstants.KubeAPIServer,
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
kubeadmconstants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
Tolerations: []v1.Toleration{
|
||||
kubeadmconstants.MasterToleration,
|
||||
},
|
||||
DNSPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: kubeadmconstants.KubeControllerManager,
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
kubeadmconstants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
Tolerations: []v1.Toleration{
|
||||
kubeadmconstants.MasterToleration,
|
||||
},
|
||||
DNSPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: kubeadmconstants.KubeScheduler,
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
kubeadmconstants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
Tolerations: []v1.Toleration{
|
||||
kubeadmconstants.MasterToleration,
|
||||
},
|
||||
DNSPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
mutatePodSpec(GetDefaultMutators(), rt.component, rt.podSpec)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed mutatePodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNodeSelectorToPodSpec(t *testing.T) {
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
kubeadmconstants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"foo": "bar",
|
||||
kubeadmconstants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
addNodeSelectorToPodSpec(rt.podSpec)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed addNodeSelectorToPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMasterTolerationOnPodSpec(t *testing.T) {
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
Tolerations: []v1.Toleration{
|
||||
kubeadmconstants.MasterToleration,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
Tolerations: []v1.Toleration{
|
||||
{Key: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
Tolerations: []v1.Toleration{
|
||||
{Key: "foo", Value: "bar"},
|
||||
kubeadmconstants.MasterToleration,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
setMasterTolerationOnPodSpec(rt.podSpec)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed setMasterTolerationOnPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRightDNSPolicyOnPodSpec(t *testing.T) {
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{},
|
||||
expected: v1.PodSpec{
|
||||
DNSPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
},
|
||||
},
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
DNSPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
setRightDNSPolicyOnPodSpec(rt.podSpec)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed setRightDNSPolicyOnPodSpec:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSelfHostedVolumesForAPIServer(t *testing.T) {
|
||||
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: "/etc/kubernetes/pki",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/pki",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: "/etc/kubernetes/pki",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: apiServerCertificatesVolumeSource(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
setSelfHostedVolumesForAPIServer(rt.podSpec)
|
||||
sort.Strings(rt.podSpec.Containers[0].Command)
|
||||
sort.Strings(rt.expected.Containers[0].Command)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed setSelfHostedVolumesForAPIServer:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSelfHostedVolumesForControllerManager(t *testing.T) {
|
||||
hostPathFileOrCreate := v1.HostPathFileOrCreate
|
||||
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: "/etc/kubernetes/pki",
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/controller-manager.conf",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--kubeconfig=/etc/kubernetes/controller-manager.conf",
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/pki",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/controller-manager.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
MountPath: "/etc/kubernetes/pki",
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/kubeconfig",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--kubeconfig=/etc/kubernetes/kubeconfig/controller-manager.conf",
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "ca-certs",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/ssl/certs",
|
||||
Type: &hostPathDirectoryOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "k8s-certs",
|
||||
VolumeSource: controllerManagerCertificatesVolumeSource(),
|
||||
},
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: kubeConfigVolumeSource(kubeadmconstants.ControllerManagerKubeConfigFileName),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
setSelfHostedVolumesForControllerManager(rt.podSpec)
|
||||
sort.Strings(rt.podSpec.Containers[0].Command)
|
||||
sort.Strings(rt.expected.Containers[0].Command)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed setSelfHostedVolumesForControllerManager:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSelfHostedVolumesForScheduler(t *testing.T) {
|
||||
hostPathFileOrCreate := v1.HostPathFileOrCreate
|
||||
var tests = []struct {
|
||||
podSpec *v1.PodSpec
|
||||
expected v1.PodSpec
|
||||
}{
|
||||
{
|
||||
podSpec: &v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/scheduler.conf",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--kubeconfig=/etc/kubernetes/scheduler.conf",
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/etc/kubernetes/scheduler.conf",
|
||||
Type: &hostPathFileOrCreate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/etc/kubernetes/kubeconfig",
|
||||
},
|
||||
},
|
||||
Command: []string{
|
||||
"--kubeconfig=/etc/kubernetes/kubeconfig/scheduler.conf",
|
||||
"--foo=bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: kubeConfigVolumeSource(kubeadmconstants.SchedulerKubeConfigFileName),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
setSelfHostedVolumesForScheduler(rt.podSpec)
|
||||
sort.Strings(rt.podSpec.Containers[0].Command)
|
||||
sort.Strings(rt.expected.Containers[0].Command)
|
||||
|
||||
if !reflect.DeepEqual(*rt.podSpec, rt.expected) {
|
||||
t.Errorf("failed setSelfHostedVolumesForScheduler:\nexpected:\n%v\nsaw:\n%v", rt.expected, *rt.podSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
185
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting.go
generated
vendored
Normal file
185
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting.go
generated
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kuberuntime "k8s.io/apimachinery/pkg/runtime"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// selfHostingWaitTimeout describes the maximum amount of time a self-hosting wait process should wait before timing out
|
||||
selfHostingWaitTimeout = 2 * time.Minute
|
||||
|
||||
// selfHostingFailureThreshold describes how many times kubeadm will retry creating the DaemonSets
|
||||
selfHostingFailureThreshold int = 5
|
||||
)
|
||||
|
||||
// CreateSelfHostedControlPlane is responsible for turning a Static Pod-hosted control plane to a self-hosted one
|
||||
// It achieves that task this way:
|
||||
// 1. Load the Static Pod specification from disk (from /etc/kubernetes/manifests)
|
||||
// 2. Extract the PodSpec from that Static Pod specification
|
||||
// 3. Mutate the PodSpec to be compatible with self-hosting (add the right labels, taints, etc. so it can schedule correctly)
|
||||
// 4. Build a new DaemonSet object for the self-hosted component in question. Use the above mentioned PodSpec
|
||||
// 5. Create the DaemonSet resource. Wait until the Pods are running.
|
||||
// 6. Remove the Static Pod manifest file. The kubelet will stop the original Static Pod-hosted component that was running.
|
||||
// 7. The self-hosted containers should now step up and take over.
|
||||
// 8. In order to avoid race conditions, we have to make sure that static pod is deleted correctly before we continue
|
||||
// Otherwise, there is a race condition when we proceed without kubelet having restarted the API server correctly and the next .Create call flakes
|
||||
// 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop
|
||||
func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, waiter apiclient.Waiter) error {
|
||||
|
||||
// Adjust the timeout slightly to something self-hosting specific
|
||||
waiter.SetTimeout(selfHostingWaitTimeout)
|
||||
|
||||
// Here the map of different mutators to use for the control plane's podspec is stored
|
||||
mutators := GetMutatorsFromFeatureGates(cfg.FeatureGates)
|
||||
|
||||
// Some extra work to be done if we should store the control plane certificates in Secrets
|
||||
if features.Enabled(cfg.FeatureGates, features.StoreCertsInSecrets) {
|
||||
|
||||
// Upload the certificates and kubeconfig files from disk to the cluster as Secrets
|
||||
if err := uploadTLSSecrets(client, cfg.CertificatesDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := uploadKubeConfigSecrets(client, kubeConfigDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, componentName := range kubeadmconstants.MasterComponents {
|
||||
start := time.Now()
|
||||
manifestPath := kubeadmconstants.GetStaticPodFilepath(componentName, manifestsDir)
|
||||
|
||||
// Since we want this function to be idempotent; just continue and try the next component if this file doesn't exist
|
||||
if _, err := os.Stat(manifestPath); err != nil {
|
||||
fmt.Printf("[self-hosted] The Static Pod for the component %q doesn't seem to be on the disk; trying the next one\n", componentName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Load the Static Pod file in order to be able to create a self-hosted variant of that file
|
||||
podSpec, err := loadPodSpecFromFile(manifestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build a DaemonSet object from the loaded PodSpec
|
||||
ds := BuildDaemonSet(componentName, podSpec, mutators)
|
||||
|
||||
// Create or update the DaemonSet in the API Server, and retry selfHostingFailureThreshold times if it errors out
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
return apiclient.CreateOrUpdateDaemonSet(client, ds)
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the self-hosted component to come up
|
||||
if err := waiter.WaitForPodsWithLabel(BuildSelfHostedComponentLabelQuery(componentName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the old Static Pod manifest
|
||||
if err := os.RemoveAll(manifestPath); err != nil {
|
||||
return fmt.Errorf("unable to delete static pod manifest for %s [%v]", componentName, err)
|
||||
}
|
||||
|
||||
// Wait for the mirror Pod hash to be removed; otherwise we'll run into race conditions here when the kubelet hasn't had time to
|
||||
// remove the Static Pod (or the mirror Pod respectively). This implicitely also tests that the API server endpoint is healthy,
|
||||
// because this blocks until the API server returns a 404 Not Found when getting the Static Pod
|
||||
staticPodName := fmt.Sprintf("%s-%s", componentName, cfg.NodeName)
|
||||
if err := waiter.WaitForPodToDisappear(staticPodName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Just as an extra safety check; make sure the API server is returning ok at the /healthz endpoint (although we know it could return a GET answer for a Pod above)
|
||||
if err := waiter.WaitForAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("[self-hosted] self-hosted %s ready after %f seconds\n", componentName, time.Since(start).Seconds())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildDaemonSet is responsible for mutating the PodSpec and return a DaemonSet which is suitable for the self-hosting purporse
|
||||
func BuildDaemonSet(name string, podSpec *v1.PodSpec, mutators map[string][]PodSpecMutatorFunc) *extensions.DaemonSet {
|
||||
|
||||
// Mutate the PodSpec so it's suitable for self-hosting
|
||||
mutatePodSpec(mutators, name, podSpec)
|
||||
|
||||
// Return a DaemonSet based on that Spec
|
||||
return &extensions.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: kubeadmconstants.AddSelfHostedPrefix(name),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Labels: BuildSelfhostedComponentLabels(name),
|
||||
},
|
||||
Spec: extensions.DaemonSetSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: BuildSelfhostedComponentLabels(name),
|
||||
},
|
||||
Spec: *podSpec,
|
||||
},
|
||||
UpdateStrategy: extensions.DaemonSetUpdateStrategy{
|
||||
// Make the DaemonSet utilize the RollingUpdate rollout strategy
|
||||
Type: extensions.RollingUpdateDaemonSetStrategyType,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// loadPodSpecFromFile reads and decodes a file containing a specification of a Pod
|
||||
// TODO: Consider using "k8s.io/kubernetes/pkg/volume/util".LoadPodFromFile(filename string) in the future instead.
|
||||
func loadPodSpecFromFile(manifestPath string) (*v1.PodSpec, error) {
|
||||
podBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
staticPod := &v1.Pod{}
|
||||
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), podBytes, staticPod); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode static pod %v", err)
|
||||
}
|
||||
|
||||
return &staticPod.Spec, nil
|
||||
}
|
||||
|
||||
// BuildSelfhostedComponentLabels returns the labels for a self-hosted component
|
||||
func BuildSelfhostedComponentLabels(component string) map[string]string {
|
||||
return map[string]string{
|
||||
"k8s-app": kubeadmconstants.AddSelfHostedPrefix(component),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildSelfHostedComponentLabelQuery creates the right query for matching a self-hosted Pod
|
||||
func BuildSelfHostedComponentLabelQuery(componentName string) string {
|
||||
return fmt.Sprintf("k8s-app=%s", kubeadmconstants.AddSelfHostedPrefix(componentName))
|
||||
}
|
||||
563
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go
generated
vendored
Normal file
563
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
testAPIServerPod = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
creationTimestamp: null
|
||||
name: kube-apiserver
|
||||
namespace: kube-system
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-apiserver
|
||||
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
|
||||
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
|
||||
- --secure-port=6443
|
||||
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
|
||||
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
|
||||
- --requestheader-group-headers=X-Remote-Group
|
||||
- --service-cluster-ip-range=10.96.0.0/12
|
||||
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
|
||||
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
|
||||
- --advertise-address=192.168.1.115
|
||||
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
|
||||
- --insecure-port=0
|
||||
- --experimental-bootstrap-token-auth=true
|
||||
- --requestheader-username-headers=X-Remote-User
|
||||
- --requestheader-extra-headers-prefix=X-Remote-Extra-
|
||||
- --requestheader-allowed-names=front-proxy-client
|
||||
- --admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota
|
||||
- --allow-privileged=true
|
||||
- --client-ca-file=/etc/kubernetes/pki/ca.crt
|
||||
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
|
||||
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
|
||||
- --authorization-mode=Node,RBAC
|
||||
- --etcd-servers=http://127.0.0.1:2379
|
||||
image: gcr.io/google_containers/kube-apiserver-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 6443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-apiserver
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
readOnly: true
|
||||
hostNetwork: true
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
- hostPath:
|
||||
path: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
- hostPath:
|
||||
path: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
status: {}
|
||||
`
|
||||
|
||||
testAPIServerDaemonSet = `metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-apiserver
|
||||
name: self-hosted-kube-apiserver
|
||||
namespace: kube-system
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-apiserver
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-apiserver
|
||||
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
|
||||
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
|
||||
- --secure-port=6443
|
||||
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
|
||||
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
|
||||
- --requestheader-group-headers=X-Remote-Group
|
||||
- --service-cluster-ip-range=10.96.0.0/12
|
||||
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
|
||||
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
|
||||
- --advertise-address=192.168.1.115
|
||||
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
|
||||
- --insecure-port=0
|
||||
- --experimental-bootstrap-token-auth=true
|
||||
- --requestheader-username-headers=X-Remote-User
|
||||
- --requestheader-extra-headers-prefix=X-Remote-Extra-
|
||||
- --requestheader-allowed-names=front-proxy-client
|
||||
- --admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota
|
||||
- --allow-privileged=true
|
||||
- --client-ca-file=/etc/kubernetes/pki/ca.crt
|
||||
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
|
||||
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
|
||||
- --authorization-mode=Node,RBAC
|
||||
- --etcd-servers=http://127.0.0.1:2379
|
||||
image: gcr.io/google_containers/kube-apiserver-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 6443
|
||||
scheme: HTTPS
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-apiserver
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
readOnly: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/master: ""
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/master
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
- hostPath:
|
||||
path: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
- hostPath:
|
||||
path: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
status:
|
||||
currentNumberScheduled: 0
|
||||
desiredNumberScheduled: 0
|
||||
numberMisscheduled: 0
|
||||
numberReady: 0
|
||||
`
|
||||
|
||||
testControllerManagerPod = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
creationTimestamp: null
|
||||
name: kube-controller-manager
|
||||
namespace: kube-system
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-controller-manager
|
||||
- --leader-elect=true
|
||||
- --controllers=*,bootstrapsigner,tokencleaner
|
||||
- --kubeconfig=/etc/kubernetes/controller-manager.conf
|
||||
- --root-ca-file=/etc/kubernetes/pki/ca.crt
|
||||
- --service-account-private-key-file=/etc/kubernetes/pki/sa.key
|
||||
- --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
|
||||
- --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
|
||||
- --address=127.0.0.1
|
||||
- --use-service-account-credentials=true
|
||||
image: gcr.io/google_containers/kube-controller-manager-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 10252
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-controller-manager
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/kubernetes/controller-manager.conf
|
||||
name: kubeconfig
|
||||
readOnly: true
|
||||
- mountPath: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
readOnly: true
|
||||
hostNetwork: true
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
- hostPath:
|
||||
path: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/controller-manager.conf
|
||||
type: FileOrCreate
|
||||
name: kubeconfig
|
||||
- hostPath:
|
||||
path: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
status: {}
|
||||
`
|
||||
|
||||
testControllerManagerDaemonSet = `metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-controller-manager
|
||||
name: self-hosted-kube-controller-manager
|
||||
namespace: kube-system
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-controller-manager
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-controller-manager
|
||||
- --leader-elect=true
|
||||
- --controllers=*,bootstrapsigner,tokencleaner
|
||||
- --kubeconfig=/etc/kubernetes/controller-manager.conf
|
||||
- --root-ca-file=/etc/kubernetes/pki/ca.crt
|
||||
- --service-account-private-key-file=/etc/kubernetes/pki/sa.key
|
||||
- --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
|
||||
- --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
|
||||
- --address=127.0.0.1
|
||||
- --use-service-account-credentials=true
|
||||
image: gcr.io/google_containers/kube-controller-manager-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 10252
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-controller-manager
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
readOnly: true
|
||||
- mountPath: /etc/kubernetes/controller-manager.conf
|
||||
name: kubeconfig
|
||||
readOnly: true
|
||||
- mountPath: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
readOnly: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/master: ""
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/master
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/pki
|
||||
name: k8s-certs
|
||||
- hostPath:
|
||||
path: /etc/ssl/certs
|
||||
name: ca-certs
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/controller-manager.conf
|
||||
type: FileOrCreate
|
||||
name: kubeconfig
|
||||
- hostPath:
|
||||
path: /etc/pki
|
||||
name: ca-certs-etc-pki
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
status:
|
||||
currentNumberScheduled: 0
|
||||
desiredNumberScheduled: 0
|
||||
numberMisscheduled: 0
|
||||
numberReady: 0
|
||||
`
|
||||
|
||||
testSchedulerPod = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
scheduler.alpha.kubernetes.io/critical-pod: ""
|
||||
creationTimestamp: null
|
||||
name: kube-scheduler
|
||||
namespace: kube-system
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-scheduler
|
||||
- --leader-elect=true
|
||||
- --kubeconfig=/etc/kubernetes/scheduler.conf
|
||||
- --address=127.0.0.1
|
||||
image: gcr.io/google_containers/kube-scheduler-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 10251
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-scheduler
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/scheduler.conf
|
||||
name: kubeconfig
|
||||
readOnly: true
|
||||
hostNetwork: true
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/scheduler.conf
|
||||
type: FileOrCreate
|
||||
name: kubeconfig
|
||||
status: {}
|
||||
`
|
||||
|
||||
testSchedulerDaemonSet = `metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-scheduler
|
||||
name: self-hosted-kube-scheduler
|
||||
namespace: kube-system
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
k8s-app: self-hosted-kube-scheduler
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- kube-scheduler
|
||||
- --leader-elect=true
|
||||
- --kubeconfig=/etc/kubernetes/scheduler.conf
|
||||
- --address=127.0.0.1
|
||||
image: gcr.io/google_containers/kube-scheduler-amd64:v1.7.4
|
||||
livenessProbe:
|
||||
failureThreshold: 8
|
||||
httpGet:
|
||||
host: 127.0.0.1
|
||||
path: /healthz
|
||||
port: 10251
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 15
|
||||
name: kube-scheduler
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
volumeMounts:
|
||||
- mountPath: /etc/kubernetes/scheduler.conf
|
||||
name: kubeconfig
|
||||
readOnly: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/master: ""
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/master
|
||||
volumes:
|
||||
- hostPath:
|
||||
path: /etc/kubernetes/scheduler.conf
|
||||
type: FileOrCreate
|
||||
name: kubeconfig
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
status:
|
||||
currentNumberScheduled: 0
|
||||
desiredNumberScheduled: 0
|
||||
numberMisscheduled: 0
|
||||
numberReady: 0
|
||||
`
|
||||
)
|
||||
|
||||
func TestBuildDaemonSet(t *testing.T) {
|
||||
var tests = []struct {
|
||||
component string
|
||||
podBytes []byte
|
||||
dsBytes []byte
|
||||
}{
|
||||
{
|
||||
component: constants.KubeAPIServer,
|
||||
podBytes: []byte(testAPIServerPod),
|
||||
dsBytes: []byte(testAPIServerDaemonSet),
|
||||
},
|
||||
{
|
||||
component: constants.KubeControllerManager,
|
||||
podBytes: []byte(testControllerManagerPod),
|
||||
dsBytes: []byte(testControllerManagerDaemonSet),
|
||||
},
|
||||
{
|
||||
component: constants.KubeScheduler,
|
||||
podBytes: []byte(testSchedulerPod),
|
||||
dsBytes: []byte(testSchedulerDaemonSet),
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
tempFile, err := createTempFileWithContent(rt.podBytes)
|
||||
defer os.Remove(tempFile)
|
||||
|
||||
podSpec, err := loadPodSpecFromFile(tempFile)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't load the specified Pod")
|
||||
}
|
||||
|
||||
ds := BuildDaemonSet(rt.component, podSpec, GetDefaultMutators())
|
||||
dsBytes, err := yaml.Marshal(ds)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal daemonset to YAML: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(dsBytes, rt.dsBytes) {
|
||||
t.Errorf("failed TestBuildDaemonSet:\nexpected:\n%s\nsaw:\n%s", rt.dsBytes, dsBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPodSpecFromFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
content string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
// Good YAML
|
||||
content: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: testpod
|
||||
spec:
|
||||
containers:
|
||||
- image: gcr.io/google_containers/busybox
|
||||
`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
// Good JSON
|
||||
content: `
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"name": "testpod"
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"image": "gcr.io/google_containers/busybox"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
// Bad PodSpec
|
||||
content: `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: testpod
|
||||
spec:
|
||||
- image: gcr.io/google_containers/busybox
|
||||
`,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
tempFile, err := createTempFileWithContent([]byte(rt.content))
|
||||
defer os.Remove(tempFile)
|
||||
|
||||
_, err = loadPodSpecFromFile(tempFile)
|
||||
if (err != nil) != rt.expectError {
|
||||
t.Errorf("failed TestLoadPodSpecFromFile:\nexpected error:\n%t\nsaw:\n%v", rt.expectError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTempFileWithContent(content []byte) (string, error) {
|
||||
tempFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot create temporary file: %v", err)
|
||||
}
|
||||
if _, err = tempFile.Write([]byte(content)); err != nil {
|
||||
return "", fmt.Errorf("cannot save temporary file: %v", err)
|
||||
}
|
||||
if err = tempFile.Close(); err != nil {
|
||||
return "", fmt.Errorf("cannot close temporary file: %v", err)
|
||||
}
|
||||
return tempFile.Name(), nil
|
||||
}
|
||||
298
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes.go
generated
vendored
Normal file
298
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
|
||||
type tlsKeyPair struct {
|
||||
name string
|
||||
cert string
|
||||
key string
|
||||
}
|
||||
|
||||
func apiServerCertificatesVolumeSource() v1.VolumeSource {
|
||||
return v1.VolumeSource{
|
||||
Projected: &v1.ProjectedVolumeSource{
|
||||
Sources: []v1.VolumeProjection{
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.CACertName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.APIServerCertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.APIServerCertName,
|
||||
},
|
||||
{
|
||||
Key: v1.TLSPrivateKeyKey,
|
||||
Path: kubeadmconstants.APIServerKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.APIServerKubeletClientCertName,
|
||||
},
|
||||
{
|
||||
Key: v1.TLSPrivateKeyKey,
|
||||
Path: kubeadmconstants.APIServerKubeletClientKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.ServiceAccountKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.ServiceAccountPublicKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.FrontProxyCACertName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.FrontProxyClientCertName,
|
||||
},
|
||||
{
|
||||
Key: v1.TLSPrivateKeyKey,
|
||||
Path: kubeadmconstants.FrontProxyClientKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func controllerManagerCertificatesVolumeSource() v1.VolumeSource {
|
||||
return v1.VolumeSource{
|
||||
Projected: &v1.ProjectedVolumeSource{
|
||||
Sources: []v1.VolumeProjection{
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSCertKey,
|
||||
Path: kubeadmconstants.CACertName,
|
||||
},
|
||||
{
|
||||
Key: v1.TLSPrivateKeyKey,
|
||||
Path: kubeadmconstants.CAKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Secret: &v1.SecretProjection{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: kubeadmconstants.ServiceAccountKeyBaseName,
|
||||
},
|
||||
Items: []v1.KeyToPath{
|
||||
{
|
||||
Key: v1.TLSPrivateKeyKey,
|
||||
Path: kubeadmconstants.ServiceAccountPrivateKeyName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func kubeConfigVolumeSource(kubeconfigSecretName string) v1.VolumeSource {
|
||||
return v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: kubeconfigSecretName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func uploadTLSSecrets(client clientset.Interface, certDir string) error {
|
||||
for _, tlsKeyPair := range getTLSKeyPairs() {
|
||||
secret, err := createTLSSecretFromFiles(
|
||||
tlsKeyPair.name,
|
||||
filepath.Join(certDir, tlsKeyPair.cert),
|
||||
filepath.Join(certDir, tlsKeyPair.key),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := apiclient.CreateOrUpdateSecret(client, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[self-hosted] Created TLS secret %q from %s and %s\n", tlsKeyPair.name, tlsKeyPair.cert, tlsKeyPair.key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadKubeConfigSecrets(client clientset.Interface, kubeConfigDir string) error {
|
||||
files := []string{
|
||||
kubeadmconstants.SchedulerKubeConfigFileName,
|
||||
kubeadmconstants.ControllerManagerKubeConfigFileName,
|
||||
}
|
||||
for _, file := range files {
|
||||
kubeConfigPath := filepath.Join(kubeConfigDir, file)
|
||||
secret, err := createOpaqueSecretFromFile(file, kubeConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := apiclient.CreateOrUpdateSecret(client, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("[self-hosted] Created secret for kubeconfig file %q\n", file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTLSSecretFromFiles(secretName, crt, key string) (*v1.Secret, error) {
|
||||
crtBytes, err := ioutil.ReadFile(crt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, err := ioutil.ReadFile(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Type: v1.SecretTypeTLS,
|
||||
Data: map[string][]byte{
|
||||
v1.TLSCertKey: crtBytes,
|
||||
v1.TLSPrivateKeyKey: keyBytes,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createOpaqueSecretFromFile(secretName, file string) (*v1.Secret, error) {
|
||||
fileBytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
filepath.Base(file): fileBytes,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTLSKeyPairs() []*tlsKeyPair {
|
||||
return []*tlsKeyPair{
|
||||
{
|
||||
name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
cert: kubeadmconstants.CACertName,
|
||||
key: kubeadmconstants.CAKeyName,
|
||||
},
|
||||
{
|
||||
name: kubeadmconstants.APIServerCertAndKeyBaseName,
|
||||
cert: kubeadmconstants.APIServerCertName,
|
||||
key: kubeadmconstants.APIServerKeyName,
|
||||
},
|
||||
{
|
||||
name: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
|
||||
cert: kubeadmconstants.APIServerKubeletClientCertName,
|
||||
key: kubeadmconstants.APIServerKubeletClientKeyName,
|
||||
},
|
||||
{
|
||||
name: kubeadmconstants.ServiceAccountKeyBaseName,
|
||||
cert: kubeadmconstants.ServiceAccountPublicKeyName,
|
||||
key: kubeadmconstants.ServiceAccountPrivateKeyName,
|
||||
},
|
||||
{
|
||||
name: kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
cert: kubeadmconstants.FrontProxyCACertName,
|
||||
key: kubeadmconstants.FrontProxyCAKeyName,
|
||||
},
|
||||
{
|
||||
name: kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
|
||||
cert: kubeadmconstants.FrontProxyClientCertName,
|
||||
key: kubeadmconstants.FrontProxyClientKeyName,
|
||||
},
|
||||
}
|
||||
}
|
||||
72
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes_test.go
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting/selfhosting_volumes_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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 selfhosting
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createTemporaryFile(name string) *os.File {
|
||||
content := []byte("foo")
|
||||
tmpfile, err := ioutil.TempFile("", name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return tmpfile
|
||||
}
|
||||
|
||||
func TestCreateTLSSecretFromFile(t *testing.T) {
|
||||
tmpCert := createTemporaryFile("foo.crt")
|
||||
defer os.Remove(tmpCert.Name())
|
||||
tmpKey := createTemporaryFile("foo.key")
|
||||
defer os.Remove(tmpKey.Name())
|
||||
|
||||
_, err := createTLSSecretFromFiles("foo", tmpCert.Name(), tmpKey.Name())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpCert.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpKey.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOpaqueSecretFromFile(t *testing.T) {
|
||||
tmpFile := createTemporaryFile("foo")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
_, err := createOpaqueSecretFromFile("foo", tmpFile.Name())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
14
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/token/BUILD
generated
vendored
Normal file
14
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/token/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
83
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/BUILD
generated
vendored
Normal file
83
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"compute.go",
|
||||
"configuration.go",
|
||||
"health.go",
|
||||
"policy.go",
|
||||
"postupgrade.go",
|
||||
"postupgrade_v17_v18.go",
|
||||
"prepull.go",
|
||||
"selfhosted.go",
|
||||
"staticpods.go",
|
||||
"versiongetter.go",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/addons/proxy:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/apiconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/uploadconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//cmd/kubeadm/app/util/config:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"compute_test.go",
|
||||
"policy_test.go",
|
||||
"prepull_test.go",
|
||||
"staticpods_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
256
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute.go
generated
vendored
Normal file
256
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute.go
generated
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
// Upgrade defines an upgrade possibility to upgrade from a current version to a new one
|
||||
type Upgrade struct {
|
||||
Description string
|
||||
Before ClusterState
|
||||
After ClusterState
|
||||
}
|
||||
|
||||
// CanUpgradeKubelets returns whether an upgrade of any kubelet in the cluster is possible
|
||||
func (u *Upgrade) CanUpgradeKubelets() bool {
|
||||
// If there are multiple different versions now, an upgrade is possible (even if only for a subset of the nodes)
|
||||
if len(u.Before.KubeletVersions) > 1 {
|
||||
return true
|
||||
}
|
||||
// Don't report something available for upgrade if we don't know the current state
|
||||
if len(u.Before.KubeletVersions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// if the same version number existed both before and after, we don't have to upgrade it
|
||||
_, sameVersionFound := u.Before.KubeletVersions[u.After.KubeVersion]
|
||||
return !sameVersionFound
|
||||
}
|
||||
|
||||
// ClusterState describes the state of certain versions for a cluster
|
||||
type ClusterState struct {
|
||||
// KubeVersion describes the version of the Kubernetes API Server, Controller Manager, Scheduler and Proxy.
|
||||
KubeVersion string
|
||||
// DNSVersion describes the version of the kube-dns images used and manifest version
|
||||
DNSVersion string
|
||||
// KubeadmVersion describes the version of the kubeadm CLI
|
||||
KubeadmVersion string
|
||||
// KubeletVersions is a map with a version number linked to the amount of kubelets running that version in the cluster
|
||||
KubeletVersions map[string]uint16
|
||||
}
|
||||
|
||||
// GetAvailableUpgrades fetches all versions from the specified VersionGetter and computes which
|
||||
// kinds of upgrades can be performed
|
||||
func GetAvailableUpgrades(versionGetterImpl VersionGetter, experimentalUpgradesAllowed, rcUpgradesAllowed bool) ([]Upgrade, error) {
|
||||
fmt.Println("[upgrade] Fetching available versions to upgrade to:")
|
||||
|
||||
// Collect the upgrades kubeadm can do in this list
|
||||
upgrades := []Upgrade{}
|
||||
|
||||
// Get the cluster version
|
||||
clusterVersionStr, clusterVersion, err := versionGetterImpl.ClusterVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get current kubeadm CLI version
|
||||
kubeadmVersionStr, kubeadmVersion, err := versionGetterImpl.KubeadmVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get and output the current latest stable version
|
||||
stableVersionStr, stableVersion, err := versionGetterImpl.VersionFromCILabel("stable", "stable version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the kubelet versions in the cluster
|
||||
kubeletVersions, err := versionGetterImpl.KubeletVersions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Construct a descriptor for the current state of the world
|
||||
beforeState := ClusterState{
|
||||
KubeVersion: clusterVersionStr,
|
||||
DNSVersion: dns.GetKubeDNSVersion(clusterVersion),
|
||||
KubeadmVersion: kubeadmVersionStr,
|
||||
KubeletVersions: kubeletVersions,
|
||||
}
|
||||
|
||||
// Do a "dumb guess" that a new minor upgrade is available just because the latest stable version is higher than the cluster version
|
||||
// This guess will be corrected once we know if there is a patch version available
|
||||
canDoMinorUpgrade := clusterVersion.LessThan(stableVersion)
|
||||
|
||||
// A patch version doesn't exist if the cluster version is higher than or equal to the current stable version
|
||||
// in the case that a user is trying to upgrade from, let's say, v1.8.0-beta.2 to v1.8.0-rc.1 (given we support such upgrades experimentally)
|
||||
// a stable-1.8 branch doesn't exist yet. Hence this check.
|
||||
if patchVersionBranchExists(clusterVersion, stableVersion) {
|
||||
|
||||
currentBranch := getBranchFromVersion(clusterVersionStr)
|
||||
versionLabel := fmt.Sprintf("stable-%s", currentBranch)
|
||||
description := fmt.Sprintf("version in the v%s series", currentBranch)
|
||||
|
||||
// Get and output the latest patch version for the cluster branch
|
||||
patchVersionStr, patchVersion, err := versionGetterImpl.VersionFromCILabel(versionLabel, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if a minor version upgrade is possible when a patch release exists
|
||||
// It's only possible if the latest patch version is higher than the current patch version
|
||||
// If that's the case, they must be on different branches => a newer minor version can be upgraded to
|
||||
canDoMinorUpgrade = minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion)
|
||||
|
||||
// If the cluster version is lower than the newest patch version, we should inform about the possible upgrade
|
||||
if patchUpgradePossible(clusterVersion, patchVersion) {
|
||||
|
||||
// The kubeadm version has to be upgraded to the latest patch version
|
||||
newKubeadmVer := patchVersionStr
|
||||
if kubeadmVersion.AtLeast(patchVersion) {
|
||||
// In this case, the kubeadm CLI version is new enough. Don't display an update suggestion for kubeadm by making .NewKubeadmVersion equal .CurrentKubeadmVersion
|
||||
newKubeadmVer = kubeadmVersionStr
|
||||
}
|
||||
|
||||
upgrades = append(upgrades, Upgrade{
|
||||
Description: description,
|
||||
Before: beforeState,
|
||||
After: ClusterState{
|
||||
KubeVersion: patchVersionStr,
|
||||
DNSVersion: dns.GetKubeDNSVersion(patchVersion),
|
||||
KubeadmVersion: newKubeadmVer,
|
||||
// KubeletVersions is unset here as it is not used anywhere in .After
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if canDoMinorUpgrade {
|
||||
upgrades = append(upgrades, Upgrade{
|
||||
Description: "stable version",
|
||||
Before: beforeState,
|
||||
After: ClusterState{
|
||||
KubeVersion: stableVersionStr,
|
||||
DNSVersion: dns.GetKubeDNSVersion(stableVersion),
|
||||
KubeadmVersion: stableVersionStr,
|
||||
// KubeletVersions is unset here as it is not used anywhere in .After
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if experimentalUpgradesAllowed || rcUpgradesAllowed {
|
||||
// dl.k8s.io/release/latest.txt is ALWAYS an alpha.X version
|
||||
// dl.k8s.io/release/latest-1.X.txt is first v1.X.0-alpha.0 -> v1.X.0-alpha.Y, then v1.X.0-beta.0 to v1.X.0-beta.Z, then v1.X.0-rc.1 to v1.X.0-rc.W.
|
||||
// After the v1.X.0 release, latest-1.X.txt is always a beta.0 version. Let's say the latest stable version on the v1.7 branch is v1.7.3, then the
|
||||
// latest-1.7 version is v1.7.4-beta.0
|
||||
|
||||
// Worth noticing is that when the release-1.X branch is cut; there are two versions tagged: v1.X.0-beta.0 AND v1.(X+1).alpha.0
|
||||
// The v1.(X+1).alpha.0 is pretty much useless and should just be ignored, as more betas may be released that have more features than the initial v1.(X+1).alpha.0
|
||||
|
||||
// So what we do below is getting the latest overall version, always an v1.X.0-alpha.Y version. Then we get latest-1.(X-1) version. This version may be anything
|
||||
// between v1.(X-1).0-beta.0 and v1.(X-1).Z-beta.0. At some point in time, latest-1.(X-1) will point to v1.(X-1).0-rc.1. Then we should show it.
|
||||
|
||||
// The flow looks like this (with time on the X axis):
|
||||
// v1.8.0-alpha.1 -> v1.8.0-alpha.2 -> v1.8.0-alpha.3 | release-1.8 branch | v1.8.0-beta.0 -> v1.8.0-beta.1 -> v1.8.0-beta.2 -> v1.8.0-rc.1 -> v1.8.0 -> v1.8.1
|
||||
// v1.9.0-alpha.0 -> v1.9.0-alpha.1 -> v1.9.0-alpha.2
|
||||
|
||||
// Get and output the current latest unstable version
|
||||
latestVersionStr, latestVersion, err := versionGetterImpl.VersionFromCILabel("latest", "experimental version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
minorUnstable := latestVersion.Components()[1]
|
||||
// Get and output the current latest unstable version
|
||||
previousBranch := fmt.Sprintf("latest-1.%d", minorUnstable-1)
|
||||
previousBranchLatestVersionStr, previousBranchLatestVersion, err := versionGetterImpl.VersionFromCILabel(previousBranch, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If that previous latest version is an RC, RCs are allowed and the cluster version is lower than the RC version, show the upgrade
|
||||
if rcUpgradesAllowed && rcUpgradePossible(clusterVersion, previousBranchLatestVersion) {
|
||||
upgrades = append(upgrades, Upgrade{
|
||||
Description: "release candidate version",
|
||||
Before: beforeState,
|
||||
After: ClusterState{
|
||||
KubeVersion: previousBranchLatestVersionStr,
|
||||
DNSVersion: dns.GetKubeDNSVersion(previousBranchLatestVersion),
|
||||
KubeadmVersion: previousBranchLatestVersionStr,
|
||||
// KubeletVersions is unset here as it is not used anywhere in .After
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Show the possibility if experimental upgrades are allowed
|
||||
if experimentalUpgradesAllowed && clusterVersion.LessThan(latestVersion) {
|
||||
|
||||
// Default to assume that the experimental version to show is the unstable one
|
||||
unstableKubeVersion := latestVersionStr
|
||||
unstableKubeDNSVersion := dns.GetKubeDNSVersion(latestVersion)
|
||||
|
||||
// Ẃe should not display alpha.0. The previous branch's beta/rc versions are more relevant due how the kube branching process works.
|
||||
if latestVersion.PreRelease() == "alpha.0" {
|
||||
unstableKubeVersion = previousBranchLatestVersionStr
|
||||
unstableKubeDNSVersion = dns.GetKubeDNSVersion(previousBranchLatestVersion)
|
||||
}
|
||||
|
||||
upgrades = append(upgrades, Upgrade{
|
||||
Description: "experimental version",
|
||||
Before: beforeState,
|
||||
After: ClusterState{
|
||||
KubeVersion: unstableKubeVersion,
|
||||
DNSVersion: unstableKubeDNSVersion,
|
||||
KubeadmVersion: unstableKubeVersion,
|
||||
// KubeletVersions is unset here as it is not used anywhere in .After
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add a newline in the end of this output to leave some space to the next output section
|
||||
fmt.Println("")
|
||||
|
||||
return upgrades, nil
|
||||
}
|
||||
|
||||
func getBranchFromVersion(version string) string {
|
||||
return strings.TrimPrefix(version, "v")[:3]
|
||||
}
|
||||
|
||||
func patchVersionBranchExists(clusterVersion, stableVersion *version.Version) bool {
|
||||
return stableVersion.AtLeast(clusterVersion)
|
||||
}
|
||||
|
||||
func patchUpgradePossible(clusterVersion, patchVersion *version.Version) bool {
|
||||
return clusterVersion.LessThan(patchVersion)
|
||||
}
|
||||
|
||||
func rcUpgradePossible(clusterVersion, previousBranchLatestVersion *version.Version) bool {
|
||||
return strings.HasPrefix(previousBranchLatestVersion.PreRelease(), "rc") && clusterVersion.LessThan(previousBranchLatestVersion)
|
||||
}
|
||||
|
||||
func minorUpgradePossibleWithPatchRelease(stableVersion, patchVersion *version.Version) bool {
|
||||
return patchVersion.LessThan(stableVersion)
|
||||
}
|
||||
481
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute_test.go
generated
vendored
Normal file
481
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
versionutil "k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
type fakeVersionGetter struct {
|
||||
clusterVersion, kubeadmVersion, stableVersion, latestVersion, latestDevBranchVersion, stablePatchVersion, kubeletVersion string
|
||||
}
|
||||
|
||||
var _ VersionGetter = &fakeVersionGetter{}
|
||||
|
||||
// ClusterVersion gets a fake API server version
|
||||
func (f *fakeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) {
|
||||
return f.clusterVersion, versionutil.MustParseSemantic(f.clusterVersion), nil
|
||||
}
|
||||
|
||||
// KubeadmVersion gets a fake kubeadm version
|
||||
func (f *fakeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) {
|
||||
return f.kubeadmVersion, versionutil.MustParseSemantic(f.kubeadmVersion), nil
|
||||
}
|
||||
|
||||
// VersionFromCILabel gets fake latest versions from CI
|
||||
func (f *fakeVersionGetter) VersionFromCILabel(ciVersionLabel, _ string) (string, *versionutil.Version, error) {
|
||||
if ciVersionLabel == "stable" {
|
||||
return f.stableVersion, versionutil.MustParseSemantic(f.stableVersion), nil
|
||||
}
|
||||
if ciVersionLabel == "latest" {
|
||||
return f.latestVersion, versionutil.MustParseSemantic(f.latestVersion), nil
|
||||
}
|
||||
if ciVersionLabel == "latest-1.8" {
|
||||
return f.latestDevBranchVersion, versionutil.MustParseSemantic(f.latestDevBranchVersion), nil
|
||||
}
|
||||
return f.stablePatchVersion, versionutil.MustParseSemantic(f.stablePatchVersion), nil
|
||||
}
|
||||
|
||||
// KubeletVersions gets the versions of the kubelets in the cluster
|
||||
func (f *fakeVersionGetter) KubeletVersions() (map[string]uint16, error) {
|
||||
return map[string]uint16{
|
||||
f.kubeletVersion: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestGetAvailableUpgrades(t *testing.T) {
|
||||
tests := []struct {
|
||||
vg *fakeVersionGetter
|
||||
expectedUpgrades []Upgrade
|
||||
allowExperimental, allowRCs bool
|
||||
errExpected bool
|
||||
}{
|
||||
{ // no action needed, already up-to-date
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
|
||||
stablePatchVersion: "v1.7.3",
|
||||
stableVersion: "v1.7.3",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{},
|
||||
allowExperimental: false,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // simple patch version upgrade
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.1",
|
||||
kubeletVersion: "v1.7.1", // the kubelet are on the same version as the control plane
|
||||
kubeadmVersion: "v1.7.2",
|
||||
|
||||
stablePatchVersion: "v1.7.3",
|
||||
stableVersion: "v1.7.3",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "version in the v1.7 series",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.1",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.1": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.2",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.7.3",
|
||||
KubeadmVersion: "v1.7.3",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: false,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // minor version upgrade only
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3", // the kubelet are on the same version as the control plane
|
||||
kubeadmVersion: "v1.8.0",
|
||||
|
||||
stablePatchVersion: "v1.7.3",
|
||||
stableVersion: "v1.8.0",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "stable version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.3",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.3": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.8.0",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0",
|
||||
KubeadmVersion: "v1.8.0",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: false,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // both minor version upgrade and patch version upgrade available
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3", // the kubelet are on the same version as the control plane
|
||||
kubeadmVersion: "v1.8.1",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.8.2",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "version in the v1.7 series",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.3",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.3": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.8.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeadmVersion: "v1.8.1", // Note: The kubeadm version mustn't be "downgraded" here
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "stable version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.3",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.3": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.8.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.2",
|
||||
KubeadmVersion: "v1.8.2",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: false,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // allow experimental upgrades, but no upgrade available
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.8.0-alpha.2",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestVersion: "v1.8.0-alpha.2",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{},
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // upgrade to an unstable version should be supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.5",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestVersion: "v1.8.0-alpha.2",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "experimental version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-alpha.2",
|
||||
KubeadmVersion: "v1.8.0-alpha.2",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // upgrade from an unstable version to an unstable version should be supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.8.0-alpha.1",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestVersion: "v1.8.0-alpha.2",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "experimental version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.8.0-alpha.1",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-alpha.2",
|
||||
KubeadmVersion: "v1.8.0-alpha.2",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // v1.X.0-alpha.0 should be ignored
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.5",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestDevBranchVersion: "v1.8.0-beta.1",
|
||||
latestVersion: "v1.9.0-alpha.0",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "experimental version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-beta.1",
|
||||
KubeadmVersion: "v1.8.0-beta.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // upgrade to an RC version should be supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.5",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestDevBranchVersion: "v1.8.0-rc.1",
|
||||
latestVersion: "v1.9.0-alpha.1",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "release candidate version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-rc.1",
|
||||
KubeadmVersion: "v1.8.0-rc.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowRCs: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // it is possible (but very uncommon) that the latest version from the previous branch is an rc and the current latest version is alpha.0. In that case, show the RC
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.5",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestDevBranchVersion: "v1.8.0-rc.1",
|
||||
latestVersion: "v1.9.0-alpha.0",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "experimental version", // Note that this is considered an experimental version in this uncommon scenario
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-rc.1",
|
||||
KubeadmVersion: "v1.8.0-rc.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
{ // upgrade to an RC version should be supported. There may also be an even newer unstable version.
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.5",
|
||||
kubeletVersion: "v1.7.5",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
|
||||
stablePatchVersion: "v1.7.5",
|
||||
stableVersion: "v1.7.5",
|
||||
latestDevBranchVersion: "v1.8.0-rc.1",
|
||||
latestVersion: "v1.9.0-alpha.1",
|
||||
},
|
||||
expectedUpgrades: []Upgrade{
|
||||
{
|
||||
Description: "release candidate version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.8.0-rc.1",
|
||||
KubeadmVersion: "v1.8.0-rc.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "experimental version",
|
||||
Before: ClusterState{
|
||||
KubeVersion: "v1.7.5",
|
||||
KubeletVersions: map[string]uint16{
|
||||
"v1.7.5": 1,
|
||||
},
|
||||
KubeadmVersion: "v1.7.5",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: "v1.9.0-alpha.1",
|
||||
KubeadmVersion: "v1.9.0-alpha.1",
|
||||
DNSVersion: "1.14.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
allowRCs: true,
|
||||
allowExperimental: true,
|
||||
errExpected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
|
||||
actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs)
|
||||
if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) {
|
||||
t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades)
|
||||
}
|
||||
if (actualErr != nil) != rt.errExpected {
|
||||
t.Errorf("failed TestGetAvailableUpgrades\n\texpected error: %t\n\tgot error: %t", rt.errExpected, (actualErr != nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeletUpgrade(t *testing.T) {
|
||||
tests := []struct {
|
||||
before map[string]uint16
|
||||
after string
|
||||
expected bool
|
||||
}{
|
||||
{ // upgrade available
|
||||
before: map[string]uint16{
|
||||
"v1.7.1": 1,
|
||||
},
|
||||
after: "v1.7.3",
|
||||
expected: true,
|
||||
},
|
||||
{ // upgrade available
|
||||
before: map[string]uint16{
|
||||
"v1.7.1": 1,
|
||||
"v1.7.3": 100,
|
||||
},
|
||||
after: "v1.7.3",
|
||||
expected: true,
|
||||
},
|
||||
{ // upgrade not available
|
||||
before: map[string]uint16{
|
||||
"v1.7.3": 1,
|
||||
},
|
||||
after: "v1.7.3",
|
||||
expected: false,
|
||||
},
|
||||
{ // upgrade not available
|
||||
before: map[string]uint16{
|
||||
"v1.7.3": 100,
|
||||
},
|
||||
after: "v1.7.3",
|
||||
expected: false,
|
||||
},
|
||||
{ // upgrade not available if we don't know anything about the earlier state
|
||||
before: map[string]uint16{},
|
||||
after: "v1.7.3",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
|
||||
upgrade := Upgrade{
|
||||
Before: ClusterState{
|
||||
KubeletVersions: rt.before,
|
||||
},
|
||||
After: ClusterState{
|
||||
KubeVersion: rt.after,
|
||||
},
|
||||
}
|
||||
actual := upgrade.CanUpgradeKubelets()
|
||||
if actual != rt.expected {
|
||||
t.Errorf("failed TestKubeletUpgrade\n\texpected: %t\n\tgot: %t\n\ttest object: %v", rt.expected, actual, upgrade)
|
||||
}
|
||||
}
|
||||
}
|
||||
107
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/configuration.go
generated
vendored
Normal file
107
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/configuration.go
generated
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// FetchConfiguration fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
|
||||
func FetchConfiguration(client clientset.Interface, w io.Writer, cfgPath string) (*kubeadmapiext.MasterConfiguration, error) {
|
||||
fmt.Println("[upgrade/config] Making sure the configuration is correct:")
|
||||
|
||||
// Load the configuration from a file or the cluster
|
||||
configBytes, err := loadConfigurationBytes(client, w, cfgPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Take the versioned configuration populated from the configmap, default it and validate
|
||||
// Return the internal version of the API object
|
||||
versionedcfg, err := bytesToValidatedMasterConfig(configBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decode configuration: %v", err)
|
||||
}
|
||||
return versionedcfg, nil
|
||||
}
|
||||
|
||||
// loadConfigurationBytes loads the configuration byte slice from either a file or the cluster ConfigMap
|
||||
func loadConfigurationBytes(client clientset.Interface, w io.Writer, cfgPath string) ([]byte, error) {
|
||||
if cfgPath != "" {
|
||||
fmt.Printf("[upgrade/config] Reading configuration options from a file: %s\n", cfgPath)
|
||||
return ioutil.ReadFile(cfgPath)
|
||||
}
|
||||
|
||||
fmt.Println("[upgrade/config] Reading configuration from the cluster...")
|
||||
|
||||
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.MasterConfigurationConfigMap, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
fmt.Printf("[upgrade/config] In order to upgrade, a ConfigMap called %q in the %s namespace must exist.\n", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem)
|
||||
fmt.Println("[upgrade/config] Without this information, 'kubeadm upgrade' don't how to configure your upgraded cluster.")
|
||||
fmt.Println("")
|
||||
fmt.Println("[upgrade/config] Next steps:")
|
||||
fmt.Printf("\t- OPTION 1: Run 'kubeadm config upload from-flags' and specify the same CLI arguments you passed to 'kubeadm init' when you created your master.\n")
|
||||
fmt.Printf("\t- OPTION 2: Run 'kubeadm config upload from-file' and specify the same config file you passed to 'kubeadm init' when you created your master.\n")
|
||||
fmt.Printf("\t- OPTION 3: Pass a config file to 'kubeadm upgrade' using the --config flag.\n")
|
||||
fmt.Println("")
|
||||
return []byte{}, fmt.Errorf("the ConfigMap %q in the %s namespace used for getting configuration information was not found", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem)
|
||||
} else if err != nil {
|
||||
return []byte{}, fmt.Errorf("an unexpected error happened when trying to get the ConfigMap %q in the %s namespace: %v", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem, err)
|
||||
}
|
||||
|
||||
fmt.Printf("[upgrade/config] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", metav1.NamespaceSystem, constants.MasterConfigurationConfigMap)
|
||||
return []byte(configMap.Data[constants.MasterConfigurationConfigMapKey]), nil
|
||||
}
|
||||
|
||||
// bytesToValidatedMasterConfig converts a byte array to an external, defaulted and validated configuration object
|
||||
func bytesToValidatedMasterConfig(b []byte) (*kubeadmapiext.MasterConfiguration, error) {
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
finalCfg := &kubeadmapiext.MasterConfiguration{}
|
||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||
|
||||
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), b, cfg); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode config from bytes: %v", err)
|
||||
}
|
||||
// Default and convert to the internal version
|
||||
api.Scheme.Default(cfg)
|
||||
api.Scheme.Convert(cfg, internalcfg, nil)
|
||||
|
||||
// Applies dynamic defaults to settings not provided with flags
|
||||
if err := configutil.SetInitDynamicDefaults(internalcfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Validates cfg (flags/configs + defaults + dynamic defaults)
|
||||
if err := validation.ValidateMasterConfiguration(internalcfg).ToAggregate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Finally converts back to the external version
|
||||
api.Scheme.Convert(internalcfg, finalCfg, nil)
|
||||
return finalCfg, nil
|
||||
}
|
||||
206
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/health.go
generated
vendored
Normal file
206
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/health.go
generated
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
// healthCheck is a helper struct for easily performing healthchecks against the cluster and printing the output
|
||||
type healthCheck struct {
|
||||
description, okMessage, failMessage string
|
||||
// f is invoked with a k8s client passed to it. Should return an optional warning and/or an error
|
||||
f func(clientset.Interface) error
|
||||
}
|
||||
|
||||
// CheckClusterHealth makes sure:
|
||||
// - the API /healthz endpoint is healthy
|
||||
// - all Nodes are Ready
|
||||
// - (if self-hosted) that there are DaemonSets with at least one Pod for all control plane components
|
||||
// - (if static pod-hosted) that all required Static Pod manifests exist on disk
|
||||
func CheckClusterHealth(client clientset.Interface) error {
|
||||
fmt.Println("[upgrade] Making sure the cluster is healthy:")
|
||||
|
||||
healthChecks := []healthCheck{
|
||||
{
|
||||
description: "API Server health",
|
||||
okMessage: "Healthy",
|
||||
failMessage: "Unhealthy",
|
||||
f: apiServerHealthy,
|
||||
},
|
||||
{
|
||||
description: "Node health",
|
||||
okMessage: "All Nodes are healthy",
|
||||
failMessage: "More than one Node unhealthy",
|
||||
f: nodesHealthy,
|
||||
},
|
||||
// TODO: Add a check for ComponentStatuses here?
|
||||
}
|
||||
|
||||
// Run slightly different health checks depending on control plane hosting type
|
||||
if IsControlPlaneSelfHosted(client) {
|
||||
healthChecks = append(healthChecks, healthCheck{
|
||||
description: "Control plane DaemonSet health",
|
||||
okMessage: "All control plane DaemonSets are healthy",
|
||||
failMessage: "More than one control plane DaemonSet unhealthy",
|
||||
f: controlPlaneHealth,
|
||||
})
|
||||
} else {
|
||||
healthChecks = append(healthChecks, healthCheck{
|
||||
description: "Static Pod manifests exists on disk",
|
||||
okMessage: "All manifests exist on disk",
|
||||
failMessage: "Some manifests don't exist on disk",
|
||||
f: staticPodManifestHealth,
|
||||
})
|
||||
}
|
||||
|
||||
return runHealthChecks(client, healthChecks)
|
||||
}
|
||||
|
||||
// runHealthChecks runs a set of health checks against the cluster
|
||||
func runHealthChecks(client clientset.Interface, healthChecks []healthCheck) error {
|
||||
for _, check := range healthChecks {
|
||||
|
||||
err := check.f(client)
|
||||
if err != nil {
|
||||
fmt.Printf("[upgrade/health] Checking %s: %s\n", check.description, check.failMessage)
|
||||
return fmt.Errorf("The cluster is not in an upgradeable state due to: %v", err)
|
||||
}
|
||||
fmt.Printf("[upgrade/health] Checking %s: %s\n", check.description, check.okMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// apiServerHealthy checks whether the API server's /healthz endpoint is healthy
|
||||
func apiServerHealthy(client clientset.Interface) error {
|
||||
healthStatus := 0
|
||||
|
||||
// If client.Discovery().RESTClient() is nil, the fake client is used, and that means we are dry-running. Just proceed
|
||||
if client.Discovery().RESTClient() == nil {
|
||||
return nil
|
||||
}
|
||||
client.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus)
|
||||
if healthStatus != http.StatusOK {
|
||||
return fmt.Errorf("the API Server is unhealthy; /healthz didn't return %q", "ok")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nodesHealthy checks whether all Nodes in the cluster are in the Running state
|
||||
func nodesHealthy(client clientset.Interface) error {
|
||||
nodes, err := client.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't list all nodes in cluster: %v", err)
|
||||
}
|
||||
|
||||
notReadyNodes := getNotReadyNodes(nodes.Items)
|
||||
if len(notReadyNodes) != 0 {
|
||||
return fmt.Errorf("there are NotReady Nodes in the cluster: %v", notReadyNodes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// controlPlaneHealth ensures all control plane DaemonSets are healthy
|
||||
func controlPlaneHealth(client clientset.Interface) error {
|
||||
notReadyDaemonSets, err := getNotReadyDaemonSets(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(notReadyDaemonSets) != 0 {
|
||||
return fmt.Errorf("there are control plane DaemonSets in the cluster that are not ready: %v", notReadyDaemonSets)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// staticPodManifestHealth makes sure the required static pods are presents
|
||||
func staticPodManifestHealth(_ clientset.Interface) error {
|
||||
nonExistentManifests := []string{}
|
||||
for _, component := range constants.MasterComponents {
|
||||
manifestFile := constants.GetStaticPodFilepath(component, constants.GetStaticPodDirectory())
|
||||
if _, err := os.Stat(manifestFile); os.IsNotExist(err) {
|
||||
nonExistentManifests = append(nonExistentManifests, manifestFile)
|
||||
}
|
||||
}
|
||||
if len(nonExistentManifests) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("The control plane seems to be Static Pod-hosted, but some of the manifests don't seem to exist on disk. This probably means you're running 'kubeadm upgrade' on a remote machine, which is not supported for a Static Pod-hosted cluster. Manifest files not found: %v", nonExistentManifests)
|
||||
}
|
||||
|
||||
// IsControlPlaneSelfHosted returns whether the control plane is self hosted or not
|
||||
func IsControlPlaneSelfHosted(client clientset.Interface) bool {
|
||||
notReadyDaemonSets, err := getNotReadyDaemonSets(client)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If there are no NotReady DaemonSets, we are using self-hosting
|
||||
return len(notReadyDaemonSets) == 0
|
||||
}
|
||||
|
||||
// getNotReadyDaemonSets gets the amount of Ready control plane DaemonSets
|
||||
func getNotReadyDaemonSets(client clientset.Interface) ([]error, error) {
|
||||
notReadyDaemonSets := []error{}
|
||||
for _, component := range constants.MasterComponents {
|
||||
dsName := constants.AddSelfHostedPrefix(component)
|
||||
ds, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get daemonset %q in the %s namespace", dsName, metav1.NamespaceSystem)
|
||||
}
|
||||
|
||||
if err := daemonSetHealth(&ds.Status); err != nil {
|
||||
notReadyDaemonSets = append(notReadyDaemonSets, fmt.Errorf("DaemonSet %q not healthy: %v", dsName, err))
|
||||
}
|
||||
}
|
||||
return notReadyDaemonSets, nil
|
||||
}
|
||||
|
||||
// daemonSetHealth is a helper function for getting the health of a DaemonSet's status
|
||||
func daemonSetHealth(dsStatus *extensions.DaemonSetStatus) error {
|
||||
if dsStatus.CurrentNumberScheduled != dsStatus.DesiredNumberScheduled {
|
||||
return fmt.Errorf("current number of scheduled Pods ('%d') doesn't match the amount of desired Pods ('%d')", dsStatus.CurrentNumberScheduled, dsStatus.DesiredNumberScheduled)
|
||||
}
|
||||
if dsStatus.NumberAvailable == 0 {
|
||||
return fmt.Errorf("no available Pods for DaemonSet")
|
||||
}
|
||||
if dsStatus.NumberReady == 0 {
|
||||
return fmt.Errorf("no ready Pods for DaemonSet")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNotReadyNodes returns a string slice of nodes in the cluster that are NotReady
|
||||
func getNotReadyNodes(nodes []v1.Node) []string {
|
||||
notReadyNodes := []string{}
|
||||
for _, node := range nodes {
|
||||
for _, condition := range node.Status.Conditions {
|
||||
if condition.Type == v1.NodeReady && condition.Status != v1.ConditionTrue {
|
||||
notReadyNodes = append(notReadyNodes, node.ObjectMeta.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return notReadyNodes
|
||||
}
|
||||
158
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/policy.go
generated
vendored
Normal file
158
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/policy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaximumAllowedMinorVersionUpgradeSkew describes how many minor versions kubeadm can upgrade the control plane version in one go
|
||||
MaximumAllowedMinorVersionUpgradeSkew = 1
|
||||
|
||||
// MaximumAllowedMinorVersionKubeletSkew describes how many minor versions the control plane version and the kubelet can skew in a kubeadm cluster
|
||||
MaximumAllowedMinorVersionKubeletSkew = 1
|
||||
)
|
||||
|
||||
// VersionSkewPolicyErrors describes version skew errors that might be seen during the validation process in EnforceVersionPolicies
|
||||
type VersionSkewPolicyErrors struct {
|
||||
Mandatory []error
|
||||
Skippable []error
|
||||
}
|
||||
|
||||
// EnforceVersionPolicies enforces that the proposed new version is compatible with all the different version skew policies
|
||||
func EnforceVersionPolicies(versionGetter VersionGetter, newK8sVersionStr string, newK8sVersion *version.Version, allowExperimentalUpgrades, allowRCUpgrades bool) *VersionSkewPolicyErrors {
|
||||
|
||||
skewErrors := &VersionSkewPolicyErrors{
|
||||
Mandatory: []error{},
|
||||
Skippable: []error{},
|
||||
}
|
||||
|
||||
clusterVersionStr, clusterVersion, err := versionGetter.ClusterVersion()
|
||||
if err != nil {
|
||||
// This case can't be forced: kubeadm has to be able to lookup cluster version for upgrades to work
|
||||
skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Unable to fetch cluster version: %v", err))
|
||||
return skewErrors
|
||||
}
|
||||
|
||||
kubeadmVersionStr, kubeadmVersion, err := versionGetter.KubeadmVersion()
|
||||
if err != nil {
|
||||
// This case can't be forced: kubeadm has to be able to lookup its version for upgrades to work
|
||||
skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Unable to fetch kubeadm version: %v", err))
|
||||
return skewErrors
|
||||
}
|
||||
|
||||
kubeletVersions, err := versionGetter.KubeletVersions()
|
||||
if err != nil {
|
||||
// This is a non-critical error; continue although kubeadm couldn't look this up
|
||||
skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Unable to fetch kubeadm version: %v", err))
|
||||
}
|
||||
|
||||
// Make sure the new version is a supported version (higher than the minimum one supported)
|
||||
if constants.MinimumControlPlaneVersion.AtLeast(newK8sVersion) {
|
||||
// This must not happen, kubeadm always supports a minimum version; and we can't go below that
|
||||
skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is equal to or lower than the minimum supported version %q. Please specify a higher version to upgrade to", newK8sVersionStr, clusterVersionStr))
|
||||
}
|
||||
|
||||
// Make sure new version is higher than the current Kubernetes version
|
||||
if clusterVersion.AtLeast(newK8sVersion) {
|
||||
// Even though we don't officially support downgrades, it "should work", and if user(s) need it and are willing to try; they can do so with --force
|
||||
skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Specified version to upgrade to %q is equal to or lower than the cluster version %q. Downgrades are not supported yet", newK8sVersionStr, clusterVersionStr))
|
||||
} else {
|
||||
// If this code path runs, it's an upgrade (this code will run most of the time)
|
||||
// kubeadm doesn't support upgrades between two minor versions; e.g. a v1.7 -> v1.9 upgrade is not supported. Enforce that here
|
||||
if newK8sVersion.Minor() > clusterVersion.Minor()+MaximumAllowedMinorVersionUpgradeSkew {
|
||||
skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is too high; kubeadm can upgrade only %d minor version at a time", newK8sVersionStr, MaximumAllowedMinorVersionUpgradeSkew))
|
||||
}
|
||||
}
|
||||
|
||||
// If the kubeadm version is lower than what we want to upgrade to; error
|
||||
if kubeadmVersion.LessThan(newK8sVersion) {
|
||||
if newK8sVersion.Minor() > kubeadmVersion.Minor() {
|
||||
// This is totally unsupported; kubeadm has no idea how it should handle a newer minor release than itself
|
||||
skewErrors.Mandatory = append(skewErrors.Mandatory, fmt.Errorf("Specified version to upgrade to %q is one minor release higher than the kubeadm minor release (%d > %d). Such an upgrade is not supported", newK8sVersionStr, newK8sVersion.Minor(), kubeadmVersion.Minor()))
|
||||
} else {
|
||||
// Upgrading to a higher patch version than kubeadm is ok if the user specifies --force. Not recommended, but possible.
|
||||
skewErrors.Skippable = append(skewErrors.Skippable, fmt.Errorf("Specified version to upgrade to %q is higher than the kubeadm version %q. Upgrade kubeadm first using the tool you used to install kubeadm", newK8sVersionStr, kubeadmVersionStr))
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if the version is unstable and the user didn't allow that
|
||||
if err = detectUnstableVersionError(newK8sVersion, newK8sVersionStr, allowExperimentalUpgrades, allowRCUpgrades); err != nil {
|
||||
skewErrors.Skippable = append(skewErrors.Skippable, err)
|
||||
}
|
||||
|
||||
// Detect if there are too old kubelets in the cluster
|
||||
// Check for nil here since this is the only case where kubeletVersions can be nil; if KubeletVersions() returned an error
|
||||
// However, it's okay to skip that check
|
||||
if kubeletVersions != nil {
|
||||
if err = detectTooOldKubelets(newK8sVersion, kubeletVersions); err != nil {
|
||||
skewErrors.Skippable = append(skewErrors.Skippable, err)
|
||||
}
|
||||
}
|
||||
|
||||
// If we did not see any errors, return nil
|
||||
if len(skewErrors.Skippable) == 0 && len(skewErrors.Mandatory) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uh oh, we encountered one or more errors, return them
|
||||
return skewErrors
|
||||
}
|
||||
|
||||
// detectUnstableVersionError is a helper function for detecting if the unstable version (if specified) is allowed to be used
|
||||
func detectUnstableVersionError(newK8sVersion *version.Version, newK8sVersionStr string, allowExperimentalUpgrades, allowRCUpgrades bool) error {
|
||||
// Short-circuit quickly if this is not an unstable version
|
||||
if len(newK8sVersion.PreRelease()) == 0 {
|
||||
return nil
|
||||
}
|
||||
// If the user has specified that unstable versions are fine, then no error should be returned
|
||||
if allowExperimentalUpgrades {
|
||||
return nil
|
||||
}
|
||||
// If this is a release candidate and we allow such ones, everything's fine
|
||||
if strings.HasPrefix(newK8sVersion.PreRelease(), "rc") && allowRCUpgrades {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Specified version to upgrade to %q is an unstable version and such upgrades weren't allowed via setting the --allow-*-upgrades flags", newK8sVersionStr)
|
||||
}
|
||||
|
||||
// detectTooOldKubelets errors out if the kubelet versions are so old that an unsupported skew would happen if the cluster was upgraded
|
||||
func detectTooOldKubelets(newK8sVersion *version.Version, kubeletVersions map[string]uint16) error {
|
||||
tooOldKubeletVersions := []string{}
|
||||
for versionStr := range kubeletVersions {
|
||||
|
||||
kubeletVersion, err := version.ParseSemantic(versionStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse kubelet version %s", versionStr)
|
||||
}
|
||||
|
||||
if newK8sVersion.Minor() > kubeletVersion.Minor()+MaximumAllowedMinorVersionKubeletSkew {
|
||||
tooOldKubeletVersions = append(tooOldKubeletVersions, versionStr)
|
||||
}
|
||||
}
|
||||
if len(tooOldKubeletVersions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("There are kubelets in this cluster that are too old that have these versions %v", tooOldKubeletVersions)
|
||||
}
|
||||
186
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/policy_test.go
generated
vendored
Normal file
186
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/policy_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
func TestEnforceVersionPolicies(t *testing.T) {
|
||||
tests := []struct {
|
||||
vg *fakeVersionGetter
|
||||
expectedMandatoryErrs int
|
||||
expectedSkippableErrs int
|
||||
allowExperimental, allowRCs bool
|
||||
newK8sVersion string
|
||||
}{
|
||||
{ // everything ok
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.5",
|
||||
},
|
||||
newK8sVersion: "v1.7.5",
|
||||
},
|
||||
{ // everything ok
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.2",
|
||||
kubeadmVersion: "v1.8.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0",
|
||||
},
|
||||
{ // downgrades not supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
},
|
||||
newK8sVersion: "v1.7.2",
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
{ // upgrades without bumping the version number not supported yet. TODO: Change this?
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
},
|
||||
newK8sVersion: "v1.7.3",
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
{ // new version must be higher than v1.7.0
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
},
|
||||
newK8sVersion: "v1.6.10",
|
||||
expectedMandatoryErrs: 1, // version must be higher than v1.7.0
|
||||
expectedSkippableErrs: 1, // version shouldn't be downgraded
|
||||
},
|
||||
{ // upgrading two minor versions in one go is not supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.9.0",
|
||||
},
|
||||
newK8sVersion: "v1.9.0",
|
||||
expectedMandatoryErrs: 1, // can't upgrade two minor versions
|
||||
expectedSkippableErrs: 1, // kubelet <-> apiserver skew too large
|
||||
},
|
||||
{ // kubeadm version must be higher than the new kube version. However, patch version skews may be forced
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
},
|
||||
newK8sVersion: "v1.7.5",
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
{ // kubeadm version must be higher than the new kube version. Trying to upgrade k8s to a higher minor version than kubeadm itself should never be supported
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.7.3",
|
||||
},
|
||||
newK8sVersion: "v1.8.0",
|
||||
expectedMandatoryErrs: 1,
|
||||
},
|
||||
{ // the maximum skew between the cluster version and the kubelet versions should be one minor version. This may be forced through though.
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.6.8",
|
||||
kubeadmVersion: "v1.8.0",
|
||||
},
|
||||
newK8sVersion: "v1.8.0",
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
{ // experimental upgrades supported if the flag is set
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.8.0-beta.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0-beta.1",
|
||||
allowExperimental: true,
|
||||
},
|
||||
{ // release candidate upgrades supported if the flag is set
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.8.0-rc.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0-rc.1",
|
||||
allowRCs: true,
|
||||
},
|
||||
{ // release candidate upgrades supported if the flag is set
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.8.0-rc.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0-rc.1",
|
||||
allowExperimental: true,
|
||||
},
|
||||
{ // the user should not be able to upgrade to an experimental version if they haven't opted into that
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.8.0-beta.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0-beta.1",
|
||||
allowRCs: true,
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
{ // the user should not be able to upgrade to an release candidate version if they haven't opted into that
|
||||
vg: &fakeVersionGetter{
|
||||
clusterVersion: "v1.7.3",
|
||||
kubeletVersion: "v1.7.3",
|
||||
kubeadmVersion: "v1.8.0-rc.1",
|
||||
},
|
||||
newK8sVersion: "v1.8.0-rc.1",
|
||||
expectedSkippableErrs: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
|
||||
newK8sVer, err := version.ParseSemantic(rt.newK8sVersion)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't parse version %s: %v", rt.newK8sVersion, err)
|
||||
}
|
||||
|
||||
actualSkewErrs := EnforceVersionPolicies(rt.vg, rt.newK8sVersion, newK8sVer, rt.allowExperimental, rt.allowRCs)
|
||||
if actualSkewErrs == nil {
|
||||
// No errors were seen. Report unit test failure if we expected to see errors
|
||||
if rt.expectedMandatoryErrs+rt.expectedSkippableErrs > 0 {
|
||||
t.Errorf("failed TestEnforceVersionPolicies\n\texpected errors but got none")
|
||||
}
|
||||
// Otherwise, just move on with the next test
|
||||
continue
|
||||
}
|
||||
|
||||
if len(actualSkewErrs.Skippable) != rt.expectedSkippableErrs {
|
||||
t.Errorf("failed TestEnforceVersionPolicies\n\texpected skippable errors: %d\n\tgot skippable errors: %d %v", rt.expectedSkippableErrs, len(actualSkewErrs.Skippable), *rt.vg)
|
||||
}
|
||||
if len(actualSkewErrs.Mandatory) != rt.expectedMandatoryErrs {
|
||||
t.Errorf("failed TestEnforceVersionPolicies\n\texpected mandatory errors: %d\n\tgot mandatory errors: %d %v", rt.expectedMandatoryErrs, len(actualSkewErrs.Mandatory), *rt.vg)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/postupgrade.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/postupgrade.go
generated
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
|
||||
nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
// PerformPostUpgradeTasks runs nearly the same functions as 'kubeadm init' would do
|
||||
// Note that the markmaster phase is left out, not needed, and no token is created as that doesn't belong to the upgrade
|
||||
func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) error {
|
||||
errs := []error{}
|
||||
|
||||
// Upload currently used configuration to the cluster
|
||||
// Note: This is done right in the beginning of cluster initialization; as we might want to make other phases
|
||||
// depend on centralized information from this source in the future
|
||||
if err := uploadconfig.UploadConfiguration(cfg, client); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Handle Bootstrap Tokens graduating to from alpha to beta in the v1.7 -> v1.8 upgrade
|
||||
// That transition requires two minor changes
|
||||
|
||||
// Remove the old ClusterRoleBinding for approving if it already exists due to the reasons outlined in the comment below
|
||||
if err := deleteOldApprovalClusterRoleBindingIfExists(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// Upgrade the Bootstrap Tokens' authentication group
|
||||
if err := upgradeBootstrapTokens(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// Upgrade the cluster-info RBAC rules
|
||||
if err := deleteWronglyNamedClusterInfoRBACRules(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Create/update RBAC rules that makes the bootstrap tokens able to post CSRs
|
||||
if err := nodebootstraptoken.AllowBootstrapTokensToPostCSRs(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Create/update RBAC rules that makes the bootstrap tokens able to get their CSRs approved automatically
|
||||
if err := nodebootstraptoken.AutoApproveNodeBootstrapTokens(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// TODO: Is this needed to do here? I think that updating cluster info should probably be separate from a normal upgrade
|
||||
// Create the cluster-info ConfigMap with the associated RBAC rules
|
||||
// if err := clusterinfo.CreateBootstrapConfigMapIfNotExists(client, kubeadmconstants.GetAdminKubeConfigPath()); err != nil {
|
||||
// return err
|
||||
//}
|
||||
// Create/update RBAC rules that makes the cluster-info ConfigMap reachable
|
||||
if err := clusterinfo.CreateClusterInfoRBACRules(client); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// TODO: This call is deprecated
|
||||
if err := apiconfig.CreateRBACRules(client, k8sVersion); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Upgrade kube-dns and kube-proxy
|
||||
if err := dns.EnsureDNSAddon(cfg, client); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := proxy.EnsureProxyAddon(cfg, client); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.NewAggregate(errs)
|
||||
}
|
||||
152
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/postupgrade_v17_v18.go
generated
vendored
Normal file
152
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/postupgrade_v17_v18.go
generated
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
|
||||
nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
oldClusterInfoRole = "system:bootstrap-signer-clusterinfo"
|
||||
)
|
||||
|
||||
// deleteOldApprovalClusterRoleBindingIfExists exists because the roleRef of the NodeAutoApproveBootstrapClusterRoleBinding changed between
|
||||
// v1.7 and v1.8, and roleRef updates are not possible. So in order to change that binding's roleRef, we have to delete it if it already exists
|
||||
// TODO: When the v1.9 cycle starts, we can remove this logic, as the kubeadm v1.9 CLI doesn't support upgrading from v1.7
|
||||
func deleteOldApprovalClusterRoleBindingIfExists(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
|
||||
// Gate this upgrade behavior for new clusters above v1.9.0-alpha.3 where this change took place
|
||||
if k8sVersion.AtLeast(constants.MinimumCSRAutoApprovalClusterRolesVersion) {
|
||||
|
||||
err := client.RbacV1beta1().ClusterRoleBindings().Delete(nodebootstraptoken.NodeAutoApproveBootstrapClusterRoleBinding, &metav1.DeleteOptions{})
|
||||
// If the binding was not found, happily continue
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// If an unexpected error occurred, return it
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// The binding was successfully deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteWronglyNamedClusterInfoRBACRules exists because the cluster-info Role's name changed from "system:bootstrap-signer-clusterinfo" in v1.7 to
|
||||
// "kubeadm:bootstrap-signer-clusterinfo" in v1.8. It was incorrectly prefixed "system:" in v1.7
|
||||
// The old, incorrectly-named Role should be removed and roleRef updates on the binding are not possible. So in order to change that binding's roleRef,
|
||||
// we have to delete it if it already exists
|
||||
// TODO: When the v1.9 cycle starts, we can remove this logic, as the kubeadm v1.9 CLI doesn't support upgrading from v1.7
|
||||
func deleteWronglyNamedClusterInfoRBACRules(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
// Gate this upgrade behavior for new clusters above v1.8.0-beta.0 where this change took place
|
||||
if k8sVersion.AtLeast(constants.UseEnableBootstrapTokenAuthFlagVersion) {
|
||||
|
||||
if err := removeOldRole(client); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := removeOldRoleBinding(client); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// The binding was successfully deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeOldRole(client clientset.Interface) error {
|
||||
err := client.RbacV1beta1().Roles(metav1.NamespacePublic).Delete(oldClusterInfoRole, &metav1.DeleteOptions{})
|
||||
// If the binding was not found, happily continue
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// If an unexpected error occurred, return it
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The role was successfully deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeOldRoleBinding(client clientset.Interface) error {
|
||||
err := client.RbacV1beta1().RoleBindings(metav1.NamespacePublic).Delete(clusterinfo.BootstrapSignerClusterRoleName, &metav1.DeleteOptions{})
|
||||
// If the binding was not found, happily continue
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// If an unexpected error occurred, return it
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The binding was successfully removed
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeBootstrapTokens handles the transition from alpha bootstrap tokens to beta. There isn't much that is changing,
|
||||
// but the group that a Bootstrap Token authenticates as changes from "system:bootstrappers" (alpha) in v1.7 to
|
||||
// "system:bootstrappers:kubeadm:default-node-token" (beta). To handle this transition correctly, the RBAC bindings earlier
|
||||
// bound to "system:bootstrappers" are now bound to "system:bootstrappers:kubeadm:default-node-token". To make v1.7 tokens
|
||||
// still valid in v1.8; this code makes sure that all tokens that were used for authentication in v1.7 have the right group
|
||||
// bound to it in v1.8.
|
||||
// TODO: When the v1.9 cycle starts, we can remove this logic, as the kubeadm v1.9 CLI doesn't support upgrading from v1.7
|
||||
func upgradeBootstrapTokens(client clientset.Interface, k8sVersion *version.Version) error {
|
||||
|
||||
// Gate this upgrade behavior for new clusters above v1.8.0-beta.0; where this BT change took place
|
||||
if k8sVersion.AtLeast(constants.UseEnableBootstrapTokenAuthFlagVersion) {
|
||||
|
||||
tokenSelector := fields.SelectorFromSet(
|
||||
map[string]string{
|
||||
api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken),
|
||||
},
|
||||
)
|
||||
listOptions := metav1.ListOptions{
|
||||
FieldSelector: tokenSelector.String(),
|
||||
}
|
||||
|
||||
secrets, err := client.CoreV1().Secrets(metav1.NamespaceSystem).List(listOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list bootstrap tokens: %v", err)
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for _, secret := range secrets.Items {
|
||||
// If this Bootstrap Token is used for authentication, the permissions it had in v1.7 should be preserved
|
||||
if bytes.Equal(secret.Data[bootstrapapi.BootstrapTokenUsageAuthentication], []byte("true")) {
|
||||
|
||||
secret.Data[bootstrapapi.BootstrapTokenExtraGroupsKey] = []byte(constants.GetNodeBootstrapTokenAuthGroup(k8sVersion))
|
||||
|
||||
// Update the Bootstrap Token Secret
|
||||
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(&secret); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.NewAggregate(errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
180
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/prepull.go
generated
vendored
Normal file
180
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/prepull.go
generated
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
|
||||
const (
|
||||
prepullPrefix = "upgrade-prepull-"
|
||||
)
|
||||
|
||||
// Prepuller defines an interface for performing a prepull operation in a create-wait-delete fashion in parallel
|
||||
type Prepuller interface {
|
||||
CreateFunc(string) error
|
||||
WaitFunc(string)
|
||||
DeleteFunc(string) error
|
||||
}
|
||||
|
||||
// DaemonSetPrepuller makes sure the control plane images are availble on all masters
|
||||
type DaemonSetPrepuller struct {
|
||||
client clientset.Interface
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
waiter apiclient.Waiter
|
||||
}
|
||||
|
||||
// NewDaemonSetPrepuller creates a new instance of the DaemonSetPrepuller struct
|
||||
func NewDaemonSetPrepuller(client clientset.Interface, waiter apiclient.Waiter, cfg *kubeadmapi.MasterConfiguration) *DaemonSetPrepuller {
|
||||
return &DaemonSetPrepuller{
|
||||
client: client,
|
||||
cfg: cfg,
|
||||
waiter: waiter,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateFunc creates a DaemonSet for making the image available on every relevant node
|
||||
func (d *DaemonSetPrepuller) CreateFunc(component string) error {
|
||||
image := images.GetCoreImage(component, d.cfg.ImageRepository, d.cfg.KubernetesVersion, d.cfg.UnifiedControlPlaneImage)
|
||||
ds := buildPrePullDaemonSet(component, image)
|
||||
|
||||
// Create the DaemonSet in the API Server
|
||||
if err := apiclient.CreateOrUpdateDaemonSet(d.client, ds); err != nil {
|
||||
return fmt.Errorf("unable to create a DaemonSet for prepulling the component %q: %v", component, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitFunc waits for all Pods in the specified DaemonSet to be in the Running state
|
||||
func (d *DaemonSetPrepuller) WaitFunc(component string) {
|
||||
fmt.Printf("[upgrade/prepull] Prepulling image for component %s.\n", component)
|
||||
d.waiter.WaitForPodsWithLabel("k8s-app=upgrade-prepull-" + component)
|
||||
}
|
||||
|
||||
// DeleteFunc deletes the DaemonSet used for making the image available on every relevant node
|
||||
func (d *DaemonSetPrepuller) DeleteFunc(component string) error {
|
||||
dsName := addPrepullPrefix(component)
|
||||
if err := apiclient.DeleteDaemonSetForeground(d.client, metav1.NamespaceSystem, dsName); err != nil {
|
||||
return fmt.Errorf("unable to cleanup the DaemonSet used for prepulling %s: %v", component, err)
|
||||
}
|
||||
fmt.Printf("[upgrade/prepull] Prepulled image for component %s.\n", component)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrepullImagesInParallel creates DaemonSets synchronously but waits in parallel for the images to pull
|
||||
func PrepullImagesInParallel(kubePrepuller Prepuller, timeout time.Duration) error {
|
||||
componentsToPrepull := constants.MasterComponents
|
||||
fmt.Printf("[upgrade/prepull] Will prepull images for components %v\n", componentsToPrepull)
|
||||
|
||||
timeoutChan := time.After(timeout)
|
||||
|
||||
// Synchronously create the DaemonSets
|
||||
for _, component := range componentsToPrepull {
|
||||
if err := kubePrepuller.CreateFunc(component); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a channel for streaming data from goroutines that run in parallell to a blocking for loop that cleans up
|
||||
prePulledChan := make(chan string, len(componentsToPrepull))
|
||||
for _, component := range componentsToPrepull {
|
||||
go func(c string) {
|
||||
// Wait as long as needed. This WaitFunc call should be blocking until completetion
|
||||
kubePrepuller.WaitFunc(c)
|
||||
// When the task is done, go ahead and cleanup by sending the name to the channel
|
||||
prePulledChan <- c
|
||||
}(component)
|
||||
}
|
||||
|
||||
// This call blocks until all expected messages are received from the channel or errors out if timeoutChan fires.
|
||||
// For every successful wait, kubePrepuller.DeleteFunc is executed
|
||||
if err := waitForItemsFromChan(timeoutChan, prePulledChan, len(componentsToPrepull), kubePrepuller.DeleteFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("[upgrade/prepull] Successfully prepulled the images for all the control plane components")
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForItemsFromChan waits for n elements from stringChan with a timeout. For every item received from stringChan, cleanupFunc is executed
|
||||
func waitForItemsFromChan(timeoutChan <-chan time.Time, stringChan chan string, n int, cleanupFunc func(string) error) error {
|
||||
i := 0
|
||||
for {
|
||||
select {
|
||||
case <-timeoutChan:
|
||||
return fmt.Errorf("The prepull operation timed out")
|
||||
case result := <-stringChan:
|
||||
i++
|
||||
// If the cleanup function errors; error here as well
|
||||
if err := cleanupFunc(result); err != nil {
|
||||
return err
|
||||
}
|
||||
if i == n {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addPrepullPrefix adds the prepull prefix for this functionality; can be used in names, labels, etc.
|
||||
func addPrepullPrefix(component string) string {
|
||||
return fmt.Sprintf("%s%s", prepullPrefix, component)
|
||||
}
|
||||
|
||||
// buildPrePullDaemonSet builds the DaemonSet that ensures the control plane image is available
|
||||
func buildPrePullDaemonSet(component, image string) *extensions.DaemonSet {
|
||||
var gracePeriodSecs int64
|
||||
return &extensions.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: addPrepullPrefix(component),
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Spec: extensions.DaemonSetSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"k8s-app": addPrepullPrefix(component),
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: component,
|
||||
Image: image,
|
||||
Command: []string{"/bin/sleep", "3600"},
|
||||
},
|
||||
},
|
||||
NodeSelector: map[string]string{
|
||||
constants.LabelNodeRoleMaster: "",
|
||||
},
|
||||
Tolerations: []v1.Toleration{constants.MasterToleration},
|
||||
TerminationGracePeriodSeconds: &gracePeriodSecs,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
145
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/prepull_test.go
generated
vendored
Normal file
145
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/prepull_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
//"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
// failedCreatePrepuller is a fake prepuller that errors for kube-controller-manager in the CreateFunc call
|
||||
type failedCreatePrepuller struct{}
|
||||
|
||||
func NewFailedCreatePrepuller() Prepuller {
|
||||
return &failedCreatePrepuller{}
|
||||
}
|
||||
|
||||
func (p *failedCreatePrepuller) CreateFunc(component string) error {
|
||||
if component == "kube-controller-manager" {
|
||||
return fmt.Errorf("boo")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *failedCreatePrepuller) WaitFunc(component string) {}
|
||||
|
||||
func (p *failedCreatePrepuller) DeleteFunc(component string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// foreverWaitPrepuller is a fake prepuller that basically waits "forever" (10 mins, but longer than the 10sec timeout)
|
||||
type foreverWaitPrepuller struct{}
|
||||
|
||||
func NewForeverWaitPrepuller() Prepuller {
|
||||
return &foreverWaitPrepuller{}
|
||||
}
|
||||
|
||||
func (p *foreverWaitPrepuller) CreateFunc(component string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *foreverWaitPrepuller) WaitFunc(component string) {
|
||||
time.Sleep(10 * time.Minute)
|
||||
}
|
||||
|
||||
func (p *foreverWaitPrepuller) DeleteFunc(component string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// failedDeletePrepuller is a fake prepuller that errors for kube-scheduler in the DeleteFunc call
|
||||
type failedDeletePrepuller struct{}
|
||||
|
||||
func NewFailedDeletePrepuller() Prepuller {
|
||||
return &failedDeletePrepuller{}
|
||||
}
|
||||
|
||||
func (p *failedDeletePrepuller) CreateFunc(component string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *failedDeletePrepuller) WaitFunc(component string) {}
|
||||
|
||||
func (p *failedDeletePrepuller) DeleteFunc(component string) error {
|
||||
if component == "kube-scheduler" {
|
||||
return fmt.Errorf("boo")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// goodPrepuller is a fake prepuller that works as expected
|
||||
type goodPrepuller struct{}
|
||||
|
||||
func NewGoodPrepuller() Prepuller {
|
||||
return &goodPrepuller{}
|
||||
}
|
||||
|
||||
func (p *goodPrepuller) CreateFunc(component string) error {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *goodPrepuller) WaitFunc(component string) {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (p *goodPrepuller) DeleteFunc(component string) error {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPrepullImagesInParallel(t *testing.T) {
|
||||
tests := []struct {
|
||||
p Prepuller
|
||||
timeout time.Duration
|
||||
expectedErr bool
|
||||
}{
|
||||
{ // should error out; create failed
|
||||
p: NewFailedCreatePrepuller(),
|
||||
timeout: 10 * time.Second,
|
||||
expectedErr: true,
|
||||
},
|
||||
{ // should error out; timeout exceeded
|
||||
p: NewForeverWaitPrepuller(),
|
||||
timeout: 10 * time.Second,
|
||||
expectedErr: true,
|
||||
},
|
||||
{ // should error out; delete failed
|
||||
p: NewFailedDeletePrepuller(),
|
||||
timeout: 10 * time.Second,
|
||||
expectedErr: true,
|
||||
},
|
||||
{ // should work just fine
|
||||
p: NewGoodPrepuller(),
|
||||
timeout: 10 * time.Second,
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
|
||||
actualErr := PrepullImagesInParallel(rt.p, rt.timeout)
|
||||
if (actualErr != nil) != rt.expectedErr {
|
||||
t.Errorf(
|
||||
"failed TestPrepullImagesInParallel\n\texpected error: %t\n\tgot: %t",
|
||||
rt.expectedErr,
|
||||
(actualErr != nil),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
272
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/selfhosted.go
generated
vendored
Normal file
272
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/selfhosted.go
generated
vendored
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// upgradeTempDSPrefix is the prefix added to the temporary DaemonSet's name used during the upgrade
|
||||
upgradeTempDSPrefix = "temp-upgrade-"
|
||||
|
||||
// upgradeTempLabel is the label key used for identifying the temporary component's DaemonSet
|
||||
upgradeTempLabel = "temp-upgrade-component"
|
||||
|
||||
// selfHostingWaitTimeout describes the maximum amount of time a self-hosting wait process should wait before timing out
|
||||
selfHostingWaitTimeout = 2 * time.Minute
|
||||
|
||||
// selfHostingFailureThreshold describes how many times kubeadm will retry creating the DaemonSets
|
||||
selfHostingFailureThreshold int = 10
|
||||
)
|
||||
|
||||
// controlPlaneComponentResources holds the relevant Pod and DaemonSet associated with a control plane component
|
||||
type controlPlaneComponentResources struct {
|
||||
pod *v1.Pod
|
||||
daemonSet *extensions.DaemonSet
|
||||
}
|
||||
|
||||
// SelfHostedControlPlane upgrades a self-hosted control plane
|
||||
// It works as follows:
|
||||
// - The client gets the currently running DaemonSets and their associated Pods used for self-hosting the control plane
|
||||
// - A temporary DaemonSet for the component in question is created; but nearly identical to the DaemonSet for the self-hosted component running right now
|
||||
// - Why use this temporary DaemonSet? Because, the RollingUpdate strategy for upgrading DaemonSets first kills the old Pod, and then adds the new one
|
||||
// - This doesn't work for self-hosted upgrades, as if you remove the only API server for instance you have in the cluster, the cluster essentially goes down
|
||||
// - So instead, a nearly identical copy of the pre-upgrade DaemonSet is created and applied to the cluster. In the beginning, this duplicate DS is just idle
|
||||
// - kubeadm waits for the temporary DaemonSet's Pod to become Running
|
||||
// - kubeadm updates the real, self-hosted component. This will result in the pre-upgrade component Pod being removed from the cluster
|
||||
// - Luckily, the temporary, backup DaemonSet now kicks in and takes over and acts as the control plane. It recognizes that a new Pod should be created,
|
||||
// - as the "real" DaemonSet is being updated.
|
||||
// - kubeadm waits for the pre-upgrade Pod to become deleted. It now takes advantage of the backup/temporary component
|
||||
// - kubeadm waits for the new, upgraded DaemonSet to become Running.
|
||||
// - Now that the new, upgraded DaemonSet is Running, we can delete the backup/temporary DaemonSet
|
||||
// - Lastly, make sure the API /healthz endpoint still is reachable
|
||||
//
|
||||
// TL;DR; This is what the flow looks like in pseudo-code:
|
||||
// for [kube-apiserver, kube-controller-manager, kube-scheduler], do:
|
||||
// 1. Self-Hosted component v1 Running
|
||||
// -> Duplicate the DaemonSet manifest
|
||||
// 2. Self-Hosted component v1 Running (active). Backup component v1 Running (passive)
|
||||
// -> Upgrade the Self-Hosted component v1 to v2.
|
||||
// -> Self-Hosted component v1 is Deleted from the cluster
|
||||
// 3. Backup component v1 Running becomes active and completes the upgrade by creating the Self-Hosted component v2 Pod (passive)
|
||||
// -> Wait for Self-Hosted component v2 to become Running
|
||||
// 4. Backup component v1 Running (active). Self-Hosted component v2 Running (passive)
|
||||
// -> Backup component v1 is Deleted
|
||||
// 5. Wait for Self-Hosted component v2 Running to become active
|
||||
// 6. Repeat for all control plane components
|
||||
func SelfHostedControlPlane(client clientset.Interface, waiter apiclient.Waiter, cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) error {
|
||||
|
||||
// Adjust the timeout slightly to something self-hosting specific
|
||||
waiter.SetTimeout(selfHostingWaitTimeout)
|
||||
|
||||
// This function returns a map of DaemonSet objects ready to post to the API server
|
||||
newControlPlaneDaemonSets := BuildUpgradedDaemonSetsFromConfig(cfg, k8sVersion)
|
||||
|
||||
controlPlaneResources, err := getCurrentControlPlaneComponentResources(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, component := range constants.MasterComponents {
|
||||
// Make a shallow copy of the current DaemonSet in order to create a new, temporary one
|
||||
tempDS := *controlPlaneResources[component].daemonSet
|
||||
|
||||
// Mutate the temp daemonset a little to be suitable for this usage (change label selectors, etc)
|
||||
mutateTempDaemonSet(&tempDS, component)
|
||||
|
||||
// Create or update the DaemonSet in the API Server, and retry selfHostingFailureThreshold times if it errors out
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
return apiclient.CreateOrUpdateDaemonSet(client, &tempDS)
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the temporary/backup self-hosted component to come up
|
||||
if err := waiter.WaitForPodsWithLabel(buildTempUpgradeDSLabelQuery(component)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newDS := newControlPlaneDaemonSets[component]
|
||||
|
||||
// Upgrade the component's self-hosted resource
|
||||
// During this upgrade; the temporary/backup component will take over
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
|
||||
if _, err := client.ExtensionsV1beta1().DaemonSets(newDS.ObjectMeta.Namespace).Update(newDS); err != nil {
|
||||
return fmt.Errorf("couldn't update self-hosted component's DaemonSet: %v", err)
|
||||
}
|
||||
return nil
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the component's old Pod to disappear
|
||||
oldPod := controlPlaneResources[component].pod
|
||||
if err := waiter.WaitForPodToDisappear(oldPod.ObjectMeta.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the main, upgraded self-hosted component to come up
|
||||
// Here we're talking to the temporary/backup component; the upgraded component is in the process of starting up
|
||||
if err := waiter.WaitForPodsWithLabel(selfhosting.BuildSelfHostedComponentLabelQuery(component)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the temporary DaemonSet, and retry selfHostingFailureThreshold times if it errors out
|
||||
// In order to pivot back to the upgraded API server, we kill the temporary/backup component
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
return apiclient.DeleteDaemonSetForeground(client, tempDS.ObjectMeta.Namespace, tempDS.ObjectMeta.Name)
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Just as an extra safety check; make sure the API server is returning ok at the /healthz endpoint
|
||||
if err := waiter.WaitForAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("[upgrade/apply] Self-hosted component %q upgraded successfully!\n", component)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildUpgradedDaemonSetsFromConfig takes a config object and the current version and returns the DaemonSet objects to post to the master
|
||||
func BuildUpgradedDaemonSetsFromConfig(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) map[string]*extensions.DaemonSet {
|
||||
// Here the map of different mutators to use for the control plane's podspec is stored
|
||||
mutators := selfhosting.GetMutatorsFromFeatureGates(cfg.FeatureGates)
|
||||
// Get the new PodSpecs to use
|
||||
controlPlanePods := controlplane.GetStaticPodSpecs(cfg, k8sVersion)
|
||||
// Store the created DaemonSets in this map
|
||||
controlPlaneDaemonSets := map[string]*extensions.DaemonSet{}
|
||||
|
||||
for _, component := range constants.MasterComponents {
|
||||
podSpec := controlPlanePods[component].Spec
|
||||
|
||||
// Build the full DaemonSet object from the PodSpec generated from the control plane phase and
|
||||
// using the self-hosting mutators available from the selfhosting phase
|
||||
ds := selfhosting.BuildDaemonSet(component, &podSpec, mutators)
|
||||
controlPlaneDaemonSets[component] = ds
|
||||
}
|
||||
return controlPlaneDaemonSets
|
||||
}
|
||||
|
||||
// addTempUpgradeDSPrefix adds the upgradeTempDSPrefix to the specified DaemonSet name
|
||||
func addTempUpgradeDSPrefix(currentName string) string {
|
||||
return fmt.Sprintf("%s%s", upgradeTempDSPrefix, currentName)
|
||||
}
|
||||
|
||||
// buildTempUpgradeLabels returns the label string-string map for identifying the temporary
|
||||
func buildTempUpgradeLabels(component string) map[string]string {
|
||||
return map[string]string{
|
||||
upgradeTempLabel: component,
|
||||
}
|
||||
}
|
||||
|
||||
// buildTempUpgradeDSLabelQuery creates the right query for matching
|
||||
func buildTempUpgradeDSLabelQuery(component string) string {
|
||||
return fmt.Sprintf("%s=%s", upgradeTempLabel, component)
|
||||
}
|
||||
|
||||
// mutateTempDaemonSet mutates the specified self-hosted DaemonSet for the specified component
|
||||
// in a way that makes it possible to post a nearly identical, temporary DaemonSet as a backup
|
||||
func mutateTempDaemonSet(tempDS *extensions.DaemonSet, component string) {
|
||||
// Prefix the name of the temporary DaemonSet with upgradeTempDSPrefix
|
||||
tempDS.ObjectMeta.Name = addTempUpgradeDSPrefix(tempDS.ObjectMeta.Name)
|
||||
// Set .Labels to something else than the "real" self-hosted components have
|
||||
tempDS.ObjectMeta.Labels = buildTempUpgradeLabels(component)
|
||||
tempDS.Spec.Selector.MatchLabels = buildTempUpgradeLabels(component)
|
||||
tempDS.Spec.Template.ObjectMeta.Labels = buildTempUpgradeLabels(component)
|
||||
// Clean all unnecessary ObjectMeta fields
|
||||
tempDS.ObjectMeta = extractRelevantObjectMeta(tempDS.ObjectMeta)
|
||||
// Reset .Status as we're posting a new object
|
||||
tempDS.Status = extensions.DaemonSetStatus{}
|
||||
}
|
||||
|
||||
// extractRelevantObjectMeta returns only the relevant parts of ObjectMeta required when creating
|
||||
// a new, identical resource. We should not POST ResourceVersion, UUIDs, etc., only the name, labels,
|
||||
// namespace and annotations should be preserved.
|
||||
func extractRelevantObjectMeta(ob metav1.ObjectMeta) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: ob.Name,
|
||||
Namespace: ob.Namespace,
|
||||
Labels: ob.Labels,
|
||||
Annotations: ob.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
// listPodsWithLabelSelector returns the relevant Pods for the given LabelSelector
|
||||
func listPodsWithLabelSelector(client clientset.Interface, kvLabel string) (*v1.PodList, error) {
|
||||
return client.CoreV1().Pods(metav1.NamespaceSystem).List(metav1.ListOptions{
|
||||
LabelSelector: kvLabel,
|
||||
})
|
||||
}
|
||||
|
||||
// getCurrentControlPlaneComponentResources returns a string-(Pod|DaemonSet) map for later use
|
||||
func getCurrentControlPlaneComponentResources(client clientset.Interface) (map[string]controlPlaneComponentResources, error) {
|
||||
controlPlaneResources := map[string]controlPlaneComponentResources{}
|
||||
|
||||
for _, component := range constants.MasterComponents {
|
||||
var podList *v1.PodList
|
||||
var currentDS *extensions.DaemonSet
|
||||
|
||||
// Get the self-hosted pod associated with the component
|
||||
podLabelSelector := selfhosting.BuildSelfHostedComponentLabelQuery(component)
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
var tryrunerr error
|
||||
podList, tryrunerr = listPodsWithLabelSelector(client, podLabelSelector)
|
||||
return tryrunerr // note that tryrunerr is most likely nil here (in successful cases)
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure that there are only one Pod with this label selector; otherwise unexpected things can happen
|
||||
if len(podList.Items) > 1 {
|
||||
return nil, fmt.Errorf("too many pods with label selector %q found in the %s namespace", podLabelSelector, metav1.NamespaceSystem)
|
||||
}
|
||||
|
||||
// Get the component's DaemonSet object
|
||||
dsName := constants.AddSelfHostedPrefix(component)
|
||||
if err := apiclient.TryRunCommand(func() error {
|
||||
var tryrunerr error
|
||||
// Try to get the current self-hosted component
|
||||
currentDS, tryrunerr = client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Get(dsName, metav1.GetOptions{})
|
||||
return tryrunerr // note that tryrunerr is most likely nil here (in successful cases)
|
||||
}, selfHostingFailureThreshold); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add the associated resources to the map to return later
|
||||
controlPlaneResources[component] = controlPlaneComponentResources{
|
||||
pod: &podList.Items[0],
|
||||
daemonSet: currentDS,
|
||||
}
|
||||
}
|
||||
return controlPlaneResources, nil
|
||||
}
|
||||
192
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/staticpods.go
generated
vendored
Normal file
192
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/staticpods.go
generated
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
|
||||
// StaticPodPathManager is responsible for tracking the directories used in the static pod upgrade transition
|
||||
type StaticPodPathManager interface {
|
||||
// MoveFile should move a file from oldPath to newPath
|
||||
MoveFile(oldPath, newPath string) error
|
||||
// RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet
|
||||
RealManifestPath(component string) string
|
||||
// RealManifestDir should point to the static pod manifest directory used by the kubelet
|
||||
RealManifestDir() string
|
||||
// TempManifestPath gets the file path for the component in the temporary directory created for generating new manifests for the upgrade
|
||||
TempManifestPath(component string) string
|
||||
// TempManifestDir should point to the temporary directory created for generating new manifests for the upgrade
|
||||
TempManifestDir() string
|
||||
// BackupManifestPath gets the file path for the component in the backup directory used for backuping manifests during the transition
|
||||
BackupManifestPath(component string) string
|
||||
// BackupManifestDir should point to the backup directory used for backuping manifests during the transition
|
||||
BackupManifestDir() string
|
||||
}
|
||||
|
||||
// KubeStaticPodPathManager is a real implementation of StaticPodPathManager that is used when upgrading a static pod cluster
|
||||
type KubeStaticPodPathManager struct {
|
||||
realManifestDir string
|
||||
tempManifestDir string
|
||||
backupManifestDir string
|
||||
}
|
||||
|
||||
// NewKubeStaticPodPathManager creates a new instance of KubeStaticPodPathManager
|
||||
func NewKubeStaticPodPathManager(realDir, tempDir, backupDir string) StaticPodPathManager {
|
||||
return &KubeStaticPodPathManager{
|
||||
realManifestDir: realDir,
|
||||
tempManifestDir: tempDir,
|
||||
backupManifestDir: backupDir,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubeStaticPodPathManagerUsingTempDirs creates a new instance of KubeStaticPodPathManager with temporary directories backing it
|
||||
func NewKubeStaticPodPathManagerUsingTempDirs(realManifestDir string) (StaticPodPathManager, error) {
|
||||
upgradedManifestsDir, err := constants.CreateTempDirForKubeadm("kubeadm-upgraded-manifests")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backupManifestsDir, err := constants.CreateTempDirForKubeadm("kubeadm-backup-manifests")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewKubeStaticPodPathManager(realManifestDir, upgradedManifestsDir, backupManifestsDir), nil
|
||||
}
|
||||
|
||||
// MoveFile should move a file from oldPath to newPath
|
||||
func (spm *KubeStaticPodPathManager) MoveFile(oldPath, newPath string) error {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
// RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet
|
||||
func (spm *KubeStaticPodPathManager) RealManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.realManifestDir)
|
||||
}
|
||||
|
||||
// RealManifestDir should point to the static pod manifest directory used by the kubelet
|
||||
func (spm *KubeStaticPodPathManager) RealManifestDir() string {
|
||||
return spm.realManifestDir
|
||||
}
|
||||
|
||||
// TempManifestPath gets the file path for the component in the temporary directory created for generating new manifests for the upgrade
|
||||
func (spm *KubeStaticPodPathManager) TempManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.tempManifestDir)
|
||||
}
|
||||
|
||||
// TempManifestDir should point to the temporary directory created for generating new manifests for the upgrade
|
||||
func (spm *KubeStaticPodPathManager) TempManifestDir() string {
|
||||
return spm.tempManifestDir
|
||||
}
|
||||
|
||||
// BackupManifestPath gets the file path for the component in the backup directory used for backuping manifests during the transition
|
||||
func (spm *KubeStaticPodPathManager) BackupManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.backupManifestDir)
|
||||
}
|
||||
|
||||
// BackupManifestDir should point to the backup directory used for backuping manifests during the transition
|
||||
func (spm *KubeStaticPodPathManager) BackupManifestDir() string {
|
||||
return spm.backupManifestDir
|
||||
}
|
||||
|
||||
// StaticPodControlPlane upgrades a static pod-hosted control plane
|
||||
func StaticPodControlPlane(waiter apiclient.Waiter, pathMgr StaticPodPathManager, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
// This string-string map stores the component name and backup filepath (if a rollback is needed).
|
||||
// If a rollback is needed,
|
||||
recoverManifests := map[string]string{}
|
||||
|
||||
beforePodHashMap, err := waiter.WaitForStaticPodControlPlaneHashes(cfg.NodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the updated static Pod manifests into the temporary directory
|
||||
fmt.Printf("[upgrade/staticpods] Writing upgraded Static Pod manifests to %q\n", pathMgr.TempManifestDir())
|
||||
err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.TempManifestDir(), cfg)
|
||||
|
||||
for _, component := range constants.MasterComponents {
|
||||
// The old manifest is here; in the /etc/kubernetes/manifests/
|
||||
currentManifestPath := pathMgr.RealManifestPath(component)
|
||||
// The new, upgraded manifest will be written here
|
||||
newManifestPath := pathMgr.TempManifestPath(component)
|
||||
// The old manifest will be moved here; into a subfolder of the temporary directory
|
||||
// If a rollback is needed, these manifests will be put back to where they where initially
|
||||
backupManifestPath := pathMgr.BackupManifestPath(component)
|
||||
|
||||
// Store the backup path in the recover list. If something goes wrong now, this component will be rolled back.
|
||||
recoverManifests[component] = backupManifestPath
|
||||
|
||||
// Move the old manifest into the old-manifests directory
|
||||
if err := pathMgr.MoveFile(currentManifestPath, backupManifestPath); err != nil {
|
||||
return rollbackOldManifests(recoverManifests, err, pathMgr)
|
||||
}
|
||||
|
||||
// Move the new manifest into the manifests directory
|
||||
if err := pathMgr.MoveFile(newManifestPath, currentManifestPath); err != nil {
|
||||
return rollbackOldManifests(recoverManifests, err, pathMgr)
|
||||
}
|
||||
|
||||
fmt.Printf("[upgrade/staticpods] Moved upgraded manifest to %q and backed up old manifest to %q\n", currentManifestPath, backupManifestPath)
|
||||
fmt.Println("[upgrade/staticpods] Waiting for the kubelet to restart the component")
|
||||
|
||||
// Wait for the mirror Pod hash to change; otherwise we'll run into race conditions here when the kubelet hasn't had time to
|
||||
// notice the removal of the Static Pod, leading to a false positive below where we check that the API endpoint is healthy
|
||||
// If we don't do this, there is a case where we remove the Static Pod manifest, kubelet is slow to react, kubeadm checks the
|
||||
// API endpoint below of the OLD Static Pod component and proceeds quickly enough, which might lead to unexpected results.
|
||||
if err := waiter.WaitForStaticPodControlPlaneHashChange(cfg.NodeName, component, beforePodHashMap[component]); err != nil {
|
||||
return rollbackOldManifests(recoverManifests, err, pathMgr)
|
||||
}
|
||||
|
||||
// Wait for the static pod component to come up and register itself as a mirror pod
|
||||
if err := waiter.WaitForPodsWithLabel("component=" + component); err != nil {
|
||||
return rollbackOldManifests(recoverManifests, err, pathMgr)
|
||||
}
|
||||
|
||||
fmt.Printf("[upgrade/staticpods] Component %q upgraded successfully!\n", component)
|
||||
}
|
||||
// Remove the temporary directories used on a best-effort (don't fail if the calls error out)
|
||||
// The calls are set here by design; we should _not_ use "defer" above as that would remove the directories
|
||||
// even in the "fail and rollback" case, where we want the directories preserved for the user.
|
||||
os.RemoveAll(pathMgr.TempManifestDir())
|
||||
os.RemoveAll(pathMgr.BackupManifestDir())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// rollbackOldManifests rolls back the backuped manifests if something went wrong
|
||||
func rollbackOldManifests(oldManifests map[string]string, origErr error, pathMgr StaticPodPathManager) error {
|
||||
errs := []error{origErr}
|
||||
for component, backupPath := range oldManifests {
|
||||
// Where we should put back the backed up manifest
|
||||
realManifestPath := pathMgr.RealManifestPath(component)
|
||||
|
||||
// Move the backup manifest back into the manifests directory
|
||||
err := pathMgr.MoveFile(backupPath, realManifestPath)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
// Let the user know there we're problems, but we tried to reçover
|
||||
return fmt.Errorf("couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced: %v", errs)
|
||||
}
|
||||
357
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/staticpods_test.go
generated
vendored
Normal file
357
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/staticpods_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
waitForHashes = "wait-for-hashes"
|
||||
waitForHashChange = "wait-for-hash-change"
|
||||
waitForPodsWithLabel = "wait-for-pods-with-label"
|
||||
|
||||
testConfiguration = `
|
||||
api:
|
||||
advertiseAddress: 1.2.3.4
|
||||
bindPort: 6443
|
||||
apiServerCertSANs: null
|
||||
apiServerExtraArgs: null
|
||||
authorizationModes:
|
||||
- Node
|
||||
- RBAC
|
||||
certificatesDir: /etc/kubernetes/pki
|
||||
cloudProvider: ""
|
||||
controllerManagerExtraArgs: null
|
||||
etcd:
|
||||
caFile: ""
|
||||
certFile: ""
|
||||
dataDir: /var/lib/etcd
|
||||
endpoints: null
|
||||
extraArgs: null
|
||||
image: ""
|
||||
keyFile: ""
|
||||
featureFlags: null
|
||||
imageRepository: gcr.io/google_containers
|
||||
kubernetesVersion: %s
|
||||
networking:
|
||||
dnsDomain: cluster.local
|
||||
podSubnet: ""
|
||||
serviceSubnet: 10.96.0.0/12
|
||||
nodeName: thegopher
|
||||
schedulerExtraArgs: null
|
||||
token: ce3aa5.5ec8455bb76b379f
|
||||
tokenTTL: 24h
|
||||
unifiedControlPlaneImage: ""
|
||||
`
|
||||
)
|
||||
|
||||
// fakeWaiter is a fake apiclient.Waiter that returns errors it was initialized with
|
||||
type fakeWaiter struct {
|
||||
errsToReturn map[string]error
|
||||
}
|
||||
|
||||
func NewFakeStaticPodWaiter(errsToReturn map[string]error) apiclient.Waiter {
|
||||
return &fakeWaiter{
|
||||
errsToReturn: errsToReturn,
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForAPI just returns a dummy nil, to indicate that the program should just proceed
|
||||
func (w *fakeWaiter) WaitForAPI() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForPodsWithLabel just returns an error if set from errsToReturn
|
||||
func (w *fakeWaiter) WaitForPodsWithLabel(kvLabel string) error {
|
||||
return w.errsToReturn[waitForPodsWithLabel]
|
||||
}
|
||||
|
||||
// WaitForPodToDisappear just returns a dummy nil, to indicate that the program should just proceed
|
||||
func (w *fakeWaiter) WaitForPodToDisappear(podName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTimeout is a no-op; we don't use it in this implementation
|
||||
func (w *fakeWaiter) SetTimeout(_ time.Duration) {}
|
||||
|
||||
// WaitForStaticPodControlPlaneHashes returns an error if set from errsToReturn
|
||||
func (w *fakeWaiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]string, error) {
|
||||
return map[string]string{}, w.errsToReturn[waitForHashes]
|
||||
}
|
||||
|
||||
// WaitForStaticPodControlPlaneHashChange returns an error if set from errsToReturn
|
||||
func (w *fakeWaiter) WaitForStaticPodControlPlaneHashChange(_, _, _ string) error {
|
||||
return w.errsToReturn[waitForHashChange]
|
||||
}
|
||||
|
||||
// WaitForHealthyKubelet returns a dummy nil just to implement the interface
|
||||
func (w *fakeWaiter) WaitForHealthyKubelet(_ time.Duration, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeStaticPodPathManager struct {
|
||||
realManifestDir string
|
||||
tempManifestDir string
|
||||
backupManifestDir string
|
||||
MoveFileFunc func(string, string) error
|
||||
}
|
||||
|
||||
func NewFakeStaticPodPathManager(moveFileFunc func(string, string) error) (StaticPodPathManager, error) {
|
||||
realManifestsDir, err := ioutil.TempDir("", "kubeadm-upgraded-manifests")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create a temporary directory for the upgrade: %v", err)
|
||||
}
|
||||
|
||||
upgradedManifestsDir, err := ioutil.TempDir("", "kubeadm-upgraded-manifests")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create a temporary directory for the upgrade: %v", err)
|
||||
}
|
||||
|
||||
backupManifestsDir, err := ioutil.TempDir("", "kubeadm-backup-manifests")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create a temporary directory for the upgrade: %v", err)
|
||||
}
|
||||
|
||||
return &fakeStaticPodPathManager{
|
||||
realManifestDir: realManifestsDir,
|
||||
tempManifestDir: upgradedManifestsDir,
|
||||
backupManifestDir: backupManifestsDir,
|
||||
MoveFileFunc: moveFileFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (spm *fakeStaticPodPathManager) MoveFile(oldPath, newPath string) error {
|
||||
return spm.MoveFileFunc(oldPath, newPath)
|
||||
}
|
||||
|
||||
func (spm *fakeStaticPodPathManager) RealManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.realManifestDir)
|
||||
}
|
||||
func (spm *fakeStaticPodPathManager) RealManifestDir() string {
|
||||
return spm.realManifestDir
|
||||
}
|
||||
|
||||
func (spm *fakeStaticPodPathManager) TempManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.tempManifestDir)
|
||||
}
|
||||
func (spm *fakeStaticPodPathManager) TempManifestDir() string {
|
||||
return spm.tempManifestDir
|
||||
}
|
||||
|
||||
func (spm *fakeStaticPodPathManager) BackupManifestPath(component string) string {
|
||||
return constants.GetStaticPodFilepath(component, spm.backupManifestDir)
|
||||
}
|
||||
func (spm *fakeStaticPodPathManager) BackupManifestDir() string {
|
||||
return spm.backupManifestDir
|
||||
}
|
||||
|
||||
func TestStaticPodControlPlane(t *testing.T) {
|
||||
tests := []struct {
|
||||
waitErrsToReturn map[string]error
|
||||
moveFileFunc func(string, string) error
|
||||
expectedErr bool
|
||||
manifestShouldChange bool
|
||||
}{
|
||||
{ // error-free case should succeed
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: false,
|
||||
manifestShouldChange: true,
|
||||
},
|
||||
{ // any wait error should result in a rollback and an abort
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: fmt.Errorf("boo! failed"),
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
{ // any wait error should result in a rollback and an abort
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: fmt.Errorf("boo! failed"),
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
{ // any wait error should result in a rollback and an abort
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: fmt.Errorf("boo! failed"),
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
{ // any path-moving error should result in a rollback and an abort
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
// fail for kube-apiserver move
|
||||
if strings.Contains(newPath, "kube-apiserver") {
|
||||
return fmt.Errorf("moving the kube-apiserver file failed")
|
||||
}
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
{ // any path-moving error should result in a rollback and an abort
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
// fail for kube-controller-manager move
|
||||
if strings.Contains(newPath, "kube-controller-manager") {
|
||||
return fmt.Errorf("moving the kube-apiserver file failed")
|
||||
}
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
{ // any path-moving error should result in a rollback and an abort; even though this is the last component (kube-apiserver and kube-controller-manager healthy)
|
||||
waitErrsToReturn: map[string]error{
|
||||
waitForHashes: nil,
|
||||
waitForHashChange: nil,
|
||||
waitForPodsWithLabel: nil,
|
||||
},
|
||||
moveFileFunc: func(oldPath, newPath string) error {
|
||||
// fail for kube-scheduler move
|
||||
if strings.Contains(newPath, "kube-scheduler") {
|
||||
return fmt.Errorf("moving the kube-apiserver file failed")
|
||||
}
|
||||
return os.Rename(oldPath, newPath)
|
||||
},
|
||||
expectedErr: true,
|
||||
manifestShouldChange: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
|
||||
waiter := NewFakeStaticPodWaiter(rt.waitErrsToReturn)
|
||||
pathMgr, err := NewFakeStaticPodPathManager(rt.moveFileFunc)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't run NewFakeStaticPodPathManager: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(pathMgr.RealManifestDir())
|
||||
defer os.RemoveAll(pathMgr.TempManifestDir())
|
||||
defer os.RemoveAll(pathMgr.BackupManifestDir())
|
||||
|
||||
oldcfg, err := getConfig("v1.7.0")
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't create config: %v", err)
|
||||
}
|
||||
// Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method
|
||||
err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), oldcfg)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't run CreateInitStaticPodManifestFiles: %v", err)
|
||||
}
|
||||
// Get a hash of the v1.7 API server manifest to compare later (was the file re-written)
|
||||
oldHash, err := getAPIServerHash(pathMgr.RealManifestDir())
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't read temp file: %v", err)
|
||||
}
|
||||
|
||||
newcfg, err := getConfig("v1.8.0")
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't create config: %v", err)
|
||||
}
|
||||
|
||||
actualErr := StaticPodControlPlane(waiter, pathMgr, newcfg)
|
||||
if (actualErr != nil) != rt.expectedErr {
|
||||
t.Errorf(
|
||||
"failed UpgradeStaticPodControlPlane\n\texpected error: %t\n\tgot: %t",
|
||||
rt.expectedErr,
|
||||
(actualErr != nil),
|
||||
)
|
||||
}
|
||||
|
||||
newHash, err := getAPIServerHash(pathMgr.RealManifestDir())
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't read temp file: %v", err)
|
||||
}
|
||||
|
||||
if (oldHash != newHash) != rt.manifestShouldChange {
|
||||
t.Errorf(
|
||||
"failed StaticPodControlPlane\n\texpected manifest change: %t\n\tgot: %t",
|
||||
rt.manifestShouldChange,
|
||||
(oldHash != newHash),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func getAPIServerHash(dir string) (string, error) {
|
||||
manifestPath := constants.GetStaticPodFilepath(constants.KubeAPIServer, dir)
|
||||
|
||||
fileBytes, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", sha256.Sum256(fileBytes)), nil
|
||||
}
|
||||
|
||||
func getConfig(version string) (*kubeadmapi.MasterConfiguration, error) {
|
||||
externalcfg := &kubeadmapiext.MasterConfiguration{}
|
||||
internalcfg := &kubeadmapi.MasterConfiguration{}
|
||||
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(fmt.Sprintf(testConfiguration, version)), externalcfg); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode config: %v", err)
|
||||
}
|
||||
api.Scheme.Convert(externalcfg, internalcfg, nil)
|
||||
return internalcfg, nil
|
||||
}
|
||||
124
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/versiongetter.go
generated
vendored
Normal file
124
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/versiongetter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
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 upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
versionutil "k8s.io/kubernetes/pkg/util/version"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
// VersionGetter defines an interface for fetching different versions.
|
||||
// Easy to implement a fake variant of this interface for unit testing
|
||||
type VersionGetter interface {
|
||||
// ClusterVersion should return the version of the cluster i.e. the API Server version
|
||||
ClusterVersion() (string, *versionutil.Version, error)
|
||||
// KubeadmVersion should return the version of the kubeadm CLI
|
||||
KubeadmVersion() (string, *versionutil.Version, error)
|
||||
// VersionFromCILabel should resolve CI labels like `latest`, `stable`, `stable-1.8`, etc. to real versions
|
||||
VersionFromCILabel(string, string) (string, *versionutil.Version, error)
|
||||
// KubeletVersions should return a map with a version and a number that describes how many kubelets there are for that version
|
||||
KubeletVersions() (map[string]uint16, error)
|
||||
}
|
||||
|
||||
// KubeVersionGetter handles the version-fetching mechanism from external sources
|
||||
type KubeVersionGetter struct {
|
||||
client clientset.Interface
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// NewKubeVersionGetter returns a new instance of KubeVersionGetter
|
||||
func NewKubeVersionGetter(client clientset.Interface, writer io.Writer) VersionGetter {
|
||||
return &KubeVersionGetter{
|
||||
client: client,
|
||||
w: writer,
|
||||
}
|
||||
}
|
||||
|
||||
// ClusterVersion gets API server version
|
||||
func (g *KubeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) {
|
||||
clusterVersionInfo, err := g.client.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Couldn't fetch cluster version from the API Server: %v", err)
|
||||
}
|
||||
fmt.Fprintf(g.w, "[upgrade/versions] Cluster version: %s\n", clusterVersionInfo.String())
|
||||
|
||||
clusterVersion, err := versionutil.ParseSemantic(clusterVersionInfo.String())
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Couldn't parse cluster version: %v", err)
|
||||
}
|
||||
return clusterVersionInfo.String(), clusterVersion, nil
|
||||
}
|
||||
|
||||
// KubeadmVersion gets kubeadm version
|
||||
func (g *KubeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) {
|
||||
kubeadmVersionInfo := version.Get()
|
||||
fmt.Fprintf(g.w, "[upgrade/versions] kubeadm version: %s\n", kubeadmVersionInfo.String())
|
||||
|
||||
kubeadmVersion, err := versionutil.ParseSemantic(kubeadmVersionInfo.String())
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Couldn't parse kubeadm version: %v", err)
|
||||
}
|
||||
return kubeadmVersionInfo.String(), kubeadmVersion, nil
|
||||
}
|
||||
|
||||
// VersionFromCILabel resolves a version label like "latest" or "stable" to an actual version using the public Kubernetes CI uploads
|
||||
func (g *KubeVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) {
|
||||
versionStr, err := kubeadmutil.KubernetesReleaseVersion(ciVersionLabel)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Couldn't fetch latest %s version from the internet: %v", description, err)
|
||||
}
|
||||
|
||||
if description != "" {
|
||||
fmt.Fprintf(g.w, "[upgrade/versions] Latest %s: %s\n", description, versionStr)
|
||||
}
|
||||
|
||||
ver, err := versionutil.ParseSemantic(versionStr)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Couldn't parse latest %s version: %v", description, err)
|
||||
}
|
||||
return versionStr, ver, nil
|
||||
}
|
||||
|
||||
// KubeletVersions gets the versions of the kubelets in the cluster
|
||||
func (g *KubeVersionGetter) KubeletVersions() (map[string]uint16, error) {
|
||||
nodes, err := g.client.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't list all nodes in cluster")
|
||||
}
|
||||
return computeKubeletVersions(nodes.Items), nil
|
||||
}
|
||||
|
||||
// computeKubeletVersions returns a string-int map that describes how many nodes are of a specific version
|
||||
func computeKubeletVersions(nodes []v1.Node) map[string]uint16 {
|
||||
kubeletVersions := map[string]uint16{}
|
||||
for _, node := range nodes {
|
||||
kver := node.Status.NodeInfo.KubeletVersion
|
||||
if _, found := kubeletVersions[kver]; !found {
|
||||
kubeletVersions[kver] = 1
|
||||
continue
|
||||
}
|
||||
kubeletVersions[kver]++
|
||||
}
|
||||
return kubeletVersions
|
||||
}
|
||||
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/BUILD
generated
vendored
Normal file
48
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["uploadconfig.go"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/apiclient:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["uploadconfig_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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 uploadconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// UploadConfiguration saves the MasterConfiguration used for later reference (when upgrading for instance)
|
||||
func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
|
||||
|
||||
fmt.Printf("[uploadconfig] Storing the configuration used in ConfigMap %q in the %q Namespace\n", kubeadmconstants.MasterConfigurationConfigMap, metav1.NamespaceSystem)
|
||||
|
||||
// Convert cfg to the external version as that's the only version of the API that can be deserialized later
|
||||
externalcfg := &kubeadmapiext.MasterConfiguration{}
|
||||
api.Scheme.Convert(cfg, externalcfg, nil)
|
||||
|
||||
cfgYaml, err := yaml.Marshal(*externalcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: kubeadmconstants.MasterConfigurationConfigMap,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
kubeadmconstants.MasterConfigurationConfigMapKey: string(cfgYaml),
|
||||
},
|
||||
})
|
||||
}
|
||||
56
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig/uploadconfig_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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 uploadconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientsetfake "k8s.io/client-go/kubernetes/fake"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestUploadConfiguration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"basic validation with correct key",
|
||||
&kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "1.7.3",
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := clientsetfake.NewSimpleClientset()
|
||||
if err := UploadConfiguration(tt.cfg, client); (err != nil) != tt.wantErr {
|
||||
t.Errorf("UploadConfiguration() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
masterCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.MasterConfigurationConfigMap, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Fail to query ConfigMap error = %v", err)
|
||||
} else if masterCfg.Data[kubeadmconstants.MasterConfigurationConfigMapKey] == "" {
|
||||
t.Errorf("Fail to find ConfigMap key = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue