mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Merge pull request #3097 from brendandburns/img
Add support for garbage collecting images.
This commit is contained in:
commit
05a0c5ca17
@ -45,7 +45,9 @@ type DockerInterface interface {
|
|||||||
StopContainer(id string, timeout uint) error
|
StopContainer(id string, timeout uint) error
|
||||||
RemoveContainer(opts docker.RemoveContainerOptions) error
|
RemoveContainer(opts docker.RemoveContainerOptions) error
|
||||||
InspectImage(image string) (*docker.Image, error)
|
InspectImage(image string) (*docker.Image, error)
|
||||||
|
ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error)
|
||||||
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
||||||
|
RemoveImage(image string) error
|
||||||
Logs(opts docker.LogsOptions) error
|
Logs(opts docker.LogsOptions) error
|
||||||
Version() (*docker.Env, error)
|
Version() (*docker.Env, error)
|
||||||
CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
|
CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
|
||||||
@ -620,3 +622,21 @@ func parseImageName(image string) (string, string) {
|
|||||||
type ContainerCommandRunner interface {
|
type ContainerCommandRunner interface {
|
||||||
RunInContainer(containerID string, cmd []string) ([]byte, error)
|
RunInContainer(containerID string, cmd []string) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUnusedImages(client DockerInterface) ([]string, error) {
|
||||||
|
// IMPORTANT: this is _unsafe_ to do while there are active pulls
|
||||||
|
// See https://github.com/docker/docker/issues/8926 for details
|
||||||
|
images, err := client.ListImages(docker.ListImagesOptions{
|
||||||
|
Filters: map[string][]string{
|
||||||
|
"dangling": {"true"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]string, len(images))
|
||||||
|
for ix := range images {
|
||||||
|
result[ix] = images[ix].ID
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,12 +32,14 @@ type FakeDockerClient struct {
|
|||||||
Container *docker.Container
|
Container *docker.Container
|
||||||
ContainerMap map[string]*docker.Container
|
ContainerMap map[string]*docker.Container
|
||||||
Image *docker.Image
|
Image *docker.Image
|
||||||
|
Images []docker.APIImages
|
||||||
Err error
|
Err error
|
||||||
called []string
|
called []string
|
||||||
Stopped []string
|
Stopped []string
|
||||||
pulled []string
|
pulled []string
|
||||||
Created []string
|
Created []string
|
||||||
Removed []string
|
Removed []string
|
||||||
|
RemovedImages util.StringSet
|
||||||
VersionInfo docker.Env
|
VersionInfo docker.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,10 +175,20 @@ func (f *FakeDockerClient) Version() (*docker.Env, error) {
|
|||||||
func (f *FakeDockerClient) CreateExec(_ docker.CreateExecOptions) (*docker.Exec, error) {
|
func (f *FakeDockerClient) CreateExec(_ docker.CreateExecOptions) (*docker.Exec, error) {
|
||||||
return &docker.Exec{"12345678"}, nil
|
return &docker.Exec{"12345678"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeDockerClient) StartExec(_ string, _ docker.StartExecOptions) error {
|
func (f *FakeDockerClient) StartExec(_ string, _ docker.StartExecOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeDockerClient) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) {
|
||||||
|
return f.Images, f.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeDockerClient) RemoveImage(image string) error {
|
||||||
|
f.RemovedImages.Insert(image)
|
||||||
|
return f.Err
|
||||||
|
}
|
||||||
|
|
||||||
// FakeDockerPuller is a stub implementation of DockerPuller.
|
// FakeDockerPuller is a stub implementation of DockerPuller.
|
||||||
type FakeDockerPuller struct {
|
type FakeDockerPuller struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
@ -109,6 +109,11 @@ type Kubelet struct {
|
|||||||
dockerIDToRef map[dockertools.DockerID]*api.ObjectReference
|
dockerIDToRef map[dockertools.DockerID]*api.ObjectReference
|
||||||
refLock sync.RWMutex
|
refLock sync.RWMutex
|
||||||
|
|
||||||
|
// Tracks active pulls. Needed to protect image garbage collection
|
||||||
|
// See: https://github.com/docker/docker/issues/8926 for details
|
||||||
|
// TODO: Remove this when (if?) that issue is fixed.
|
||||||
|
pullLock sync.RWMutex
|
||||||
|
|
||||||
// Optional, no events will be sent without it
|
// Optional, no events will be sent without it
|
||||||
etcdClient tools.EtcdClient
|
etcdClient tools.EtcdClient
|
||||||
// Optional, defaults to simple implementaiton
|
// Optional, defaults to simple implementaiton
|
||||||
@ -203,6 +208,36 @@ func (kl *Kubelet) purgeOldest(ids []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kl *Kubelet) GarbageCollectLoop() {
|
||||||
|
util.Forever(func() {
|
||||||
|
if err := kl.GarbageCollectContainers(); err != nil {
|
||||||
|
glog.Errorf("Garbage collect failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := kl.GarbageCollectImages(); err != nil {
|
||||||
|
glog.Errorf("Garbage collect images failed: %v", err)
|
||||||
|
}
|
||||||
|
}, time.Minute*1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kl *Kubelet) getUnusedImages() ([]string, error) {
|
||||||
|
kl.pullLock.Lock()
|
||||||
|
defer kl.pullLock.Unlock()
|
||||||
|
return dockertools.GetUnusedImages(kl.dockerClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kl *Kubelet) GarbageCollectImages() error {
|
||||||
|
images, err := kl.getUnusedImages()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for ix := range images {
|
||||||
|
if err := kl.dockerClient.RemoveImage(images[ix]); err != nil {
|
||||||
|
glog.Errorf("Failed to remove image: %s (%v)", images[ix], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Also enforce a maximum total number of containers.
|
// TODO: Also enforce a maximum total number of containers.
|
||||||
func (kl *Kubelet) GarbageCollectContainers() error {
|
func (kl *Kubelet) GarbageCollectContainers() error {
|
||||||
if kl.maxContainerCount == 0 {
|
if kl.maxContainerCount == 0 {
|
||||||
@ -607,10 +642,7 @@ func (kl *Kubelet) createNetworkContainer(pod *api.BoundPod) (dockertools.Docker
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
if err := kl.dockerPuller.Pull(container.Image); err != nil {
|
if err := kl.pullImage(container.Image, ref); err != nil {
|
||||||
if ref != nil {
|
|
||||||
record.Eventf(ref, "failed", "failed", "Failed to pull image %s", container.Image)
|
|
||||||
}
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -620,6 +652,18 @@ func (kl *Kubelet) createNetworkContainer(pod *api.BoundPod) (dockertools.Docker
|
|||||||
return kl.runContainer(pod, container, nil, "")
|
return kl.runContainer(pod, container, nil, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kl *Kubelet) pullImage(img string, ref *api.ObjectReference) error {
|
||||||
|
kl.pullLock.RLock()
|
||||||
|
defer kl.pullLock.RUnlock()
|
||||||
|
if err := kl.dockerPuller.Pull(img); err != nil {
|
||||||
|
if ref != nil {
|
||||||
|
record.Eventf(ref, "failed", "failed", "Failed to pull image %s", img)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Kill all containers in a pod. Returns the number of containers deleted and an error if one occurs.
|
// Kill all containers in a pod. Returns the number of containers deleted and an error if one occurs.
|
||||||
func (kl *Kubelet) killContainersInPod(pod *api.BoundPod, dockerContainers dockertools.DockerContainers) (int, error) {
|
func (kl *Kubelet) killContainersInPod(pod *api.BoundPod, dockerContainers dockertools.DockerContainers) (int, error) {
|
||||||
podFullName := GetPodFullName(pod)
|
podFullName := GetPodFullName(pod)
|
||||||
|
@ -47,7 +47,9 @@ func init() {
|
|||||||
|
|
||||||
func newTestKubelet(t *testing.T) (*Kubelet, *tools.FakeEtcdClient, *dockertools.FakeDockerClient) {
|
func newTestKubelet(t *testing.T) (*Kubelet, *tools.FakeEtcdClient, *dockertools.FakeDockerClient) {
|
||||||
fakeEtcdClient := tools.NewFakeEtcdClient(t)
|
fakeEtcdClient := tools.NewFakeEtcdClient(t)
|
||||||
fakeDocker := &dockertools.FakeDockerClient{}
|
fakeDocker := &dockertools.FakeDockerClient{
|
||||||
|
RemovedImages: util.StringSet{},
|
||||||
|
}
|
||||||
|
|
||||||
kubelet := &Kubelet{}
|
kubelet := &Kubelet{}
|
||||||
kubelet.dockerClient = fakeDocker
|
kubelet.dockerClient = fakeDocker
|
||||||
@ -1698,3 +1700,26 @@ func TestSyncPodsWithPullPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
fakeDocker.Unlock()
|
fakeDocker.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGarbageCollectImages(t *testing.T) {
|
||||||
|
kubelet, _, fakeDocker := newTestKubelet(t)
|
||||||
|
|
||||||
|
fakeDocker.Images = []docker.APIImages{
|
||||||
|
{
|
||||||
|
ID: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubelet.GarbageCollectImages(); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fakeDocker.RemovedImages) != 2 ||
|
||||||
|
!fakeDocker.RemovedImages.Has("foo") ||
|
||||||
|
!fakeDocker.RemovedImages.Has("bar") {
|
||||||
|
t.Errorf("unexpected images removed: %v", fakeDocker.RemovedImages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
@ -77,18 +76,6 @@ func ConnectToDockerOrDie(dockerEndpoint string) *docker.Client {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move this into the kubelet itself
|
|
||||||
func GarbageCollectLoop(k *Kubelet) {
|
|
||||||
func() {
|
|
||||||
util.Forever(func() {
|
|
||||||
err := k.GarbageCollectContainers()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Garbage collect failed: %v", err)
|
|
||||||
}
|
|
||||||
}, time.Minute*1)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move this into the kubelet itself
|
// TODO: move this into the kubelet itself
|
||||||
func MonitorCAdvisor(k *Kubelet, cp uint) {
|
func MonitorCAdvisor(k *Kubelet, cp uint) {
|
||||||
defer util.HandleCrash()
|
defer util.HandleCrash()
|
||||||
|
@ -269,7 +269,7 @@ func createAndInitKubelet(kc *KubeletConfig, pc *config.PodConfig) *kubelet.Kube
|
|||||||
|
|
||||||
k.BirthCry()
|
k.BirthCry()
|
||||||
|
|
||||||
go kubelet.GarbageCollectLoop(k)
|
go k.GarbageCollectLoop()
|
||||||
go kubelet.MonitorCAdvisor(k, kc.CAdvisorPort)
|
go kubelet.MonitorCAdvisor(k, kc.CAdvisorPort)
|
||||||
kubelet.InitHealthChecking(k)
|
kubelet.InitHealthChecking(k)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user