mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Add node e2e test for Docker's live-restore
This commit is contained in:
parent
b86dd9af02
commit
9f1f83020b
@ -41,6 +41,7 @@ go_library(
|
|||||||
"//test/e2e/perftype:go_default_library",
|
"//test/e2e/perftype:go_default_library",
|
||||||
"//test/e2e_node/perftype:go_default_library",
|
"//test/e2e_node/perftype:go_default_library",
|
||||||
"//vendor/github.com/blang/semver:go_default_library",
|
"//vendor/github.com/blang/semver:go_default_library",
|
||||||
|
"//vendor/github.com/coreos/go-systemd/util:go_default_library",
|
||||||
"//vendor/github.com/docker/docker/client:go_default_library",
|
"//vendor/github.com/docker/docker/client:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/github.com/google/cadvisor/client/v2:go_default_library",
|
"//vendor/github.com/google/cadvisor/client/v2:go_default_library",
|
||||||
|
@ -17,11 +17,16 @@ limitations under the License.
|
|||||||
package e2e_node
|
package e2e_node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = framework.KubeDescribe("Docker features [Feature:Docker]", func() {
|
var _ = framework.KubeDescribe("Docker features [Feature:Docker]", func() {
|
||||||
@ -70,4 +75,103 @@ var _ = framework.KubeDescribe("Docker features [Feature:Docker]", func() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("when live-restore is enabled [Serial] [Slow] [Disruptive]", func() {
|
||||||
|
It("containers should not be disrupted when the daemon shuts down and restarts", func() {
|
||||||
|
const (
|
||||||
|
podName = "live-restore-test-pod"
|
||||||
|
containerName = "live-restore-test-container"
|
||||||
|
)
|
||||||
|
|
||||||
|
isSupported, err := isDockerLiveRestoreSupported()
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
if !isSupported {
|
||||||
|
framework.Skipf("Docker live-restore is not supported.")
|
||||||
|
}
|
||||||
|
isEnabled, err := isDockerLiveRestoreEnabled()
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
if !isEnabled {
|
||||||
|
framework.Skipf("Docker live-restore is not enabled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
By("Create the test pod.")
|
||||||
|
pod := f.PodClient().CreateSync(&v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: podName},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{{
|
||||||
|
Name: containerName,
|
||||||
|
Image: "gcr.io/google_containers/nginx-slim:0.7",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
By("Ensure that the container is running before Docker is down.")
|
||||||
|
Eventually(func() bool {
|
||||||
|
return isContainerRunning(pod.Status.PodIP)
|
||||||
|
}).Should(BeTrue())
|
||||||
|
|
||||||
|
startTime1, err := getContainerStartTime(f, podName, containerName)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
By("Stop Docker daemon.")
|
||||||
|
framework.ExpectNoError(stopDockerDaemon())
|
||||||
|
isDockerDown := true
|
||||||
|
defer func() {
|
||||||
|
if isDockerDown {
|
||||||
|
By("Start Docker daemon.")
|
||||||
|
framework.ExpectNoError(startDockerDaemon())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
By("Ensure that the container is running after Docker is down.")
|
||||||
|
Consistently(func() bool {
|
||||||
|
return isContainerRunning(pod.Status.PodIP)
|
||||||
|
}).Should(BeTrue())
|
||||||
|
|
||||||
|
By("Start Docker daemon.")
|
||||||
|
framework.ExpectNoError(startDockerDaemon())
|
||||||
|
isDockerDown = false
|
||||||
|
|
||||||
|
By("Ensure that the container is running after Docker has restarted.")
|
||||||
|
Consistently(func() bool {
|
||||||
|
return isContainerRunning(pod.Status.PodIP)
|
||||||
|
}).Should(BeTrue())
|
||||||
|
|
||||||
|
By("Ensure that the container has not been restarted after Docker is restarted.")
|
||||||
|
Consistently(func() bool {
|
||||||
|
startTime2, err := getContainerStartTime(f, podName, containerName)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
return startTime1 == startTime2
|
||||||
|
}, 3*time.Second, time.Second).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// isContainerRunning returns true if the container is running by checking
|
||||||
|
// whether the server is responding, and false otherwise.
|
||||||
|
func isContainerRunning(podIP string) bool {
|
||||||
|
output, err := runCommand("curl", podIP)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.Contains(output, "Welcome to nginx!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContainerStartTime returns the start time of the container with the
|
||||||
|
// containerName of the pod having the podName.
|
||||||
|
func getContainerStartTime(f *framework.Framework, podName, containerName string) (time.Time, error) {
|
||||||
|
pod, err := f.PodClient().Get(podName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("failed to get pod %q: %v", podName, err)
|
||||||
|
}
|
||||||
|
for _, status := range pod.Status.ContainerStatuses {
|
||||||
|
if status.Name != containerName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if status.State.Running == nil {
|
||||||
|
return time.Time{}, fmt.Errorf("%v/%v is not running", podName, containerName)
|
||||||
|
}
|
||||||
|
return status.State.Running.StartedAt.Time, nil
|
||||||
|
}
|
||||||
|
return time.Time{}, fmt.Errorf("failed to find %v/%v", podName, containerName)
|
||||||
|
}
|
||||||
|
@ -21,11 +21,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
systemdutil "github.com/coreos/go-systemd/util"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDockerEndpoint = "unix:///var/run/docker.sock"
|
defaultDockerEndpoint = "unix:///var/run/docker.sock"
|
||||||
|
dockerDaemonConfigName = "/etc/docker/daemon.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getDockerAPIVersion returns the Docker's API version.
|
// getDockerAPIVersion returns the Docker's API version.
|
||||||
@ -36,7 +38,7 @@ func getDockerAPIVersion() (semver.Version, error) {
|
|||||||
}
|
}
|
||||||
version, err := c.ServerVersion(context.Background())
|
version, err := c.ServerVersion(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return semver.Version{}, fmt.Errorf("failed to get docker info: %v", err)
|
return semver.Version{}, fmt.Errorf("failed to get docker server version: %v", err)
|
||||||
}
|
}
|
||||||
return semver.MustParse(version.APIVersion + ".0"), nil
|
return semver.MustParse(version.APIVersion + ".0"), nil
|
||||||
}
|
}
|
||||||
@ -60,3 +62,51 @@ func isDockerNoNewPrivilegesSupported() (bool, error) {
|
|||||||
}
|
}
|
||||||
return version.GTE(semver.MustParse("1.23.0")), nil
|
return version.GTE(semver.MustParse("1.23.0")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isDockerLiveRestoreSupported returns true if live-restore is supported in
|
||||||
|
// the current Docker version.
|
||||||
|
func isDockerLiveRestoreSupported() (bool, error) {
|
||||||
|
version, err := getDockerAPIVersion()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return version.GTE(semver.MustParse("1.26.0")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDockerLiveRestoreEnabled returns true if live-restore is enabled in the
|
||||||
|
// Docker.
|
||||||
|
func isDockerLiveRestoreEnabled() (bool, error) {
|
||||||
|
c, err := client.NewClient(defaultDockerEndpoint, "", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to create docker client: %v", err)
|
||||||
|
}
|
||||||
|
info, err := c.Info(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get docker info: %v", err)
|
||||||
|
}
|
||||||
|
return info.LiveRestoreEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopDockerDaemon starts the Docker daemon.
|
||||||
|
func startDockerDaemon() error {
|
||||||
|
switch {
|
||||||
|
case systemdutil.IsRunningSystemd():
|
||||||
|
_, err := runCommand("systemctl", "start", "docker")
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
_, err := runCommand("service", "docker", "start")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopDockerDaemon stops the Docker daemon.
|
||||||
|
func stopDockerDaemon() error {
|
||||||
|
switch {
|
||||||
|
case systemdutil.IsRunningSystemd():
|
||||||
|
_, err := runCommand("systemctl", "stop", "docker")
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
_, err := runCommand("service", "docker", "stop")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -342,16 +342,6 @@ var _ = framework.KubeDescribe("GKE system requirements [Conformance] [Feature:G
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// runCommand runs the cmd and returns the combined stdout and stderr, or an
|
|
||||||
// error if the command failed.
|
|
||||||
func runCommand(cmd ...string) (string, error) {
|
|
||||||
output, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to run %q: %s (%s)", strings.Join(cmd, " "), err, output)
|
|
||||||
}
|
|
||||||
return string(output), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPPID returns the PPID for the pid.
|
// getPPID returns the PPID for the pid.
|
||||||
func getPPID(pid int) (int, error) {
|
func getPPID(pid int) (int, error) {
|
||||||
statusFile := "/proc/" + strconv.Itoa(pid) + "/status"
|
statusFile := "/proc/" + strconv.Itoa(pid) + "/status"
|
||||||
|
19
test/e2e_node/jenkins/cos-init-live-restore.yaml
Normal file
19
test/e2e_node/jenkins/cos-init-live-restore.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#cloud-config
|
||||||
|
|
||||||
|
runcmd:
|
||||||
|
- echo '{"live-restore":true}' > /etc/docker/daemon.json
|
||||||
|
- systemctl restart docker
|
||||||
|
- mount /tmp /tmp -o remount,exec,suid
|
||||||
|
- usermod -a -G docker jenkins
|
||||||
|
- mkdir -p /var/lib/kubelet
|
||||||
|
- mkdir -p /home/kubernetes/containerized_mounter/rootfs
|
||||||
|
- mount --bind /home/kubernetes/containerized_mounter/ /home/kubernetes/containerized_mounter/
|
||||||
|
- mount -o remount, exec /home/kubernetes/containerized_mounter/
|
||||||
|
- wget https://storage.googleapis.com/kubernetes-release/gci-mounter/mounter.tar -O /tmp/mounter.tar
|
||||||
|
- tar xvf /tmp/mounter.tar -C /home/kubernetes/containerized_mounter/rootfs
|
||||||
|
- mkdir -p /home/kubernetes/containerized_mounter/rootfs/var/lib/kubelet
|
||||||
|
- mount --rbind /var/lib/kubelet /home/kubernetes/containerized_mounter/rootfs/var/lib/kubelet
|
||||||
|
- mount --make-rshared /home/kubernetes/containerized_mounter/rootfs/var/lib/kubelet
|
||||||
|
- mount --bind /proc /home/kubernetes/containerized_mounter/rootfs/proc
|
||||||
|
- mount --bind /dev /home/kubernetes/containerized_mounter/rootfs/dev
|
||||||
|
- rm /tmp/mounter.tar
|
@ -19,4 +19,4 @@ images:
|
|||||||
cos-beta:
|
cos-beta:
|
||||||
image_regex: cos-beta-60-9592-70-0 # docker 1.13.1
|
image_regex: cos-beta-60-9592-70-0 # docker 1.13.1
|
||||||
project: cos-cloud
|
project: cos-cloud
|
||||||
metadata: "user-data<test/e2e_node/jenkins/gci-init.yaml,gci-update-strategy=update_disabled"
|
metadata: "user-data<test/e2e_node/jenkins/cos-init-live-restore.yaml,gci-update-strategy=update_disabled"
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -325,3 +326,13 @@ func newJSONEncoder(groupName string) (runtime.Encoder, error) {
|
|||||||
// the "best" version supposedly comes first in the list returned from api.Registry.EnabledVersionsForGroup
|
// the "best" version supposedly comes first in the list returned from api.Registry.EnabledVersionsForGroup
|
||||||
return api.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil
|
return api.Codecs.EncoderForVersion(info.Serializer, versions[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runCommand runs the cmd and returns the combined stdout and stderr, or an
|
||||||
|
// error if the command failed.
|
||||||
|
func runCommand(cmd ...string) (string, error) {
|
||||||
|
output, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to run %q: %s (%s)", strings.Join(cmd, " "), err, output)
|
||||||
|
}
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user