Options for using a specific volume in the registry

New --registry-volume option for using an specific volume in
the registry.

Signed-off-by: Alvaro Saurin <alvaro.saurin@gmail.com>
This commit is contained in:
Alvaro Saurin 2020-03-02 13:04:48 +01:00
parent 0ec51e1318
commit 5b7ec7e29d
No known key found for this signature in database
GPG Key ID: 7B38F4A5C322A955
7 changed files with 168 additions and 25 deletions

View File

@ -249,6 +249,7 @@ func CreateCluster(c *cli.Context) error {
RegistryEnabled: c.Bool("enable-registry"), RegistryEnabled: c.Bool("enable-registry"),
RegistryName: c.String("registry-name"), RegistryName: c.String("registry-name"),
RegistryPort: c.Int("registry-port"), RegistryPort: c.Int("registry-port"),
RegistryVolume: c.String("registry-volume"),
ServerArgs: k3sServerArgs, ServerArgs: k3sServerArgs,
Volumes: volumesSpec, Volumes: volumesSpec,
} }
@ -379,7 +380,7 @@ func DeleteCluster(c *cli.Context) error {
return fmt.Errorf(" Couldn't remove server for cluster %s\n%+v", cluster.name, err) return fmt.Errorf(" Couldn't remove server for cluster %s\n%+v", cluster.name, err)
} }
if err := disconnectRegistryFromNetwork(cluster.name); err != nil { if err := disconnectRegistryFromNetwork(cluster.name, c.IsSet("keep-registry-volume")); err != nil {
log.Warningf("Couldn't disconnect Registry from network %s\n%+v", cluster.name, err) log.Warningf("Couldn't disconnect Registry from network %s\n%+v", cluster.name, err)
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path" "path"
"strconv"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -27,11 +28,21 @@ const defaultRegistryPort = 5000
const defaultFullRegistriesPath = "/etc/rancher/k3s/registries.yaml" const defaultFullRegistriesPath = "/etc/rancher/k3s/registries.yaml"
var defaultRegistryLabels = map[string]string{ const defaultRegistryMountPath = "/var/lib/registry"
// default labels assigned to the registry container
var defaultRegistryContainerLabels = map[string]string{
"app": "k3d", "app": "k3d",
"component": "registry", "component": "registry",
} }
// default labels assigned to the registry volume
var defaultRegistryVolumeLabels = map[string]string{
"app": "k3d",
"component": "registry",
"managed": "true",
}
// NOTE: structs copied from https://github.com/rancher/k3s/blob/master/pkg/agent/templates/registry.go // NOTE: structs copied from https://github.com/rancher/k3s/blob/master/pkg/agent/templates/registry.go
// for avoiding a dependencies nightmare... // for avoiding a dependencies nightmare...
@ -137,7 +148,7 @@ func createRegistry(spec ClusterSpec) (string, error) {
containerLabels := make(map[string]string) containerLabels := make(map[string]string)
// add a standard list of labels to our registry // add a standard list of labels to our registry
for k, v := range defaultRegistryLabels { for k, v := range defaultRegistryContainerLabels {
containerLabels[k] = v containerLabels[k] = v
} }
containerLabels["created"] = time.Now().Format("2006-01-02 15:04:05") containerLabels["created"] = time.Now().Format("2006-01-02 15:04:05")
@ -160,6 +171,32 @@ func createRegistry(spec ClusterSpec) (string, error) {
} }
spec.Volumes = &Volumes{} // we do not need in the registry any of the volumes used by the other containers spec.Volumes = &Volumes{} // we do not need in the registry any of the volumes used by the other containers
if spec.RegistryVolume != "" {
vol, err := getVolume(spec.RegistryVolume, map[string]string{})
if err != nil {
return "", fmt.Errorf(" Couldn't check if volume %s exists: %w", spec.RegistryVolume, err)
}
if vol != nil {
log.Printf("Using existing volume %s for the Registry\n", spec.RegistryVolume)
} else {
log.Printf("Creating Registry volume %s...\n", spec.RegistryVolume)
// assign some labels (so we can recognize the volume later on)
volLabels := map[string]string{
"registry-name": spec.RegistryName,
"registry-port": strconv.Itoa(spec.RegistryPort),
}
for k, v := range defaultRegistryVolumeLabels {
volLabels[k] = v
}
_, err := createVolume(spec.RegistryVolume, volLabels)
if err != nil {
return "", fmt.Errorf(" Couldn't create volume %s for registry: %w", spec.RegistryVolume, err)
}
}
mount := fmt.Sprintf("%s:%s", spec.RegistryVolume, defaultRegistryMountPath)
hostConfig.Binds = []string{mount}
}
// connect the registry to this k3d network // connect the registry to this k3d network
networkingConfig := &network.NetworkingConfig{ networkingConfig := &network.NetworkingConfig{
@ -200,7 +237,7 @@ func getRegistryContainer() (string, error) {
cFilter := filters.NewArgs() cFilter := filters.NewArgs()
cFilter.Add("name", defaultRegistryContainerName) cFilter.Add("name", defaultRegistryContainerName)
// filter with the standard list of labels of our registry // filter with the standard list of labels of our registry
for k, v := range defaultRegistryLabels { for k, v := range defaultRegistryContainerLabels {
cFilter.Add("label", fmt.Sprintf("%s=%s", k, v)) cFilter.Add("label", fmt.Sprintf("%s=%s", k, v))
} }
@ -224,7 +261,7 @@ func connectRegistryToNetwork(ID string, networkID string, aliases []string) err
// disconnectRegistryFromNetwork disconnects the Registry from a Network // disconnectRegistryFromNetwork disconnects the Registry from a Network
// if the Registry container is not connected to any more networks, it is stopped // if the Registry container is not connected to any more networks, it is stopped
func disconnectRegistryFromNetwork(name string) error { func disconnectRegistryFromNetwork(name string, keepRegistryVolume bool) error {
// disconnect the registry from this cluster's network // disconnect the registry from this cluster's network
netName := k3dNetworkName(name) netName := k3dNetworkName(name)
cid, err := getRegistryContainer() cid, err := getRegistryContainer()
@ -248,9 +285,33 @@ func disconnectRegistryFromNetwork(name string) error {
} }
if len(networks) == 0 { if len(networks) == 0 {
log.Printf("...Removing the Registry\n") log.Printf("...Removing the Registry\n")
volName, err := getVolumeMountedIn(cid, defaultRegistryMountPath)
if err != nil {
log.Printf("...warning: could not detect registry volume\n")
}
if err := removeContainer(cid); err != nil { if err := removeContainer(cid); err != nil {
log.Println(err) log.Println(err)
} }
// check if the volume mounted in /var/lib/registry was managed by us. In that case (and only if
// the user does not want to keep the volume alive), delete the registry volume
if volName != "" {
vol, err := getVolume(volName, defaultRegistryVolumeLabels)
if err != nil {
return fmt.Errorf(" Couldn't remove volume for registry %s\n%w", defaultRegistryContainerName, err)
}
if vol != nil {
if keepRegistryVolume {
log.Printf("...(keeping the Registry volume %s)\n", volName)
} else {
log.Printf("...Removing the Registry volume %s\n", volName)
if err := deleteVolume(volName); err != nil {
return fmt.Errorf(" Couldn't remove volume for registry %s\n%w", defaultRegistryContainerName, err)
}
}
}
}
} }
return nil return nil

View File

@ -48,6 +48,7 @@ type ClusterSpec struct {
RegistryEnabled bool RegistryEnabled bool
RegistryName string RegistryName string
RegistryPort int RegistryPort int
RegistryVolume string
ServerArgs []string ServerArgs []string
Volumes *Volumes Volumes *Volumes
} }

View File

@ -18,9 +18,8 @@ type Volumes struct {
GroupSpecificVolumes map[string][]string GroupSpecificVolumes map[string][]string
} }
// createImageVolume will create a new docker volume used for storing image tarballs that can be loaded into the clusters // createVolume will create a new docker volume
func createImageVolume(clusterName string) (types.Volume, error) { func createVolume(volName string, volLabels map[string]string) (types.Volume, error) {
var vol types.Volume var vol types.Volume
ctx := context.Background() ctx := context.Background()
@ -29,43 +28,99 @@ func createImageVolume(clusterName string) (types.Volume, error) {
return vol, fmt.Errorf(" Couldn't create docker client\n%+v", err) return vol, fmt.Errorf(" Couldn't create docker client\n%+v", err)
} }
volName := fmt.Sprintf("k3d-%s-images", clusterName)
volumeCreateOptions := volume.VolumeCreateBody{ volumeCreateOptions := volume.VolumeCreateBody{
Name: volName, Name: volName,
Labels: map[string]string{ Labels: volLabels,
"app": "k3d",
"cluster": clusterName,
},
Driver: "local", //TODO: allow setting driver + opts Driver: "local", //TODO: allow setting driver + opts
DriverOpts: map[string]string{}, DriverOpts: map[string]string{},
} }
vol, err = docker.VolumeCreate(ctx, volumeCreateOptions) vol, err = docker.VolumeCreate(ctx, volumeCreateOptions)
if err != nil { if err != nil {
return vol, fmt.Errorf("failed to create image volume [%s] for cluster [%s]\n%+v", volName, clusterName, err) return vol, fmt.Errorf("failed to create image volume [%s]\n%+v", volName, err)
} }
return vol, nil return vol, nil
} }
// deleteImageVolume will delete the volume we created for sharing images with this cluster // deleteVolume will delete a volume
func deleteImageVolume(clusterName string) error { func deleteVolume(volName string) error {
ctx := context.Background() ctx := context.Background()
docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
return fmt.Errorf(" Couldn't create docker client\n%+v", err) return fmt.Errorf(" Couldn't create docker client\n%+v", err)
} }
volName := fmt.Sprintf("k3d-%s-images", clusterName)
if err = docker.VolumeRemove(ctx, volName, true); err != nil { if err = docker.VolumeRemove(ctx, volName, true); err != nil {
return fmt.Errorf(" Couldn't remove volume [%s] for cluster [%s]\n%+v", volName, clusterName, err) return fmt.Errorf(" Couldn't remove volume [%s]\n%+v", volName, err)
} }
return nil return nil
} }
// getVolume checks if a docker volume exists. The volume can be specified with a name and/or some labels.
func getVolume(volName string, volLabels map[string]string) (*types.Volume, error) {
ctx := context.Background()
docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, fmt.Errorf(" Couldn't create docker client: %w", err)
}
vFilter := filters.NewArgs()
if volName != "" {
vFilter.Add("name", volName)
}
for k, v := range volLabels {
vFilter.Add("label", fmt.Sprintf("%s=%s", k, v))
}
volumes, err := docker.VolumeList(ctx, vFilter)
if err != nil {
return nil, fmt.Errorf(" Couldn't list volumes: %w", err)
}
if len(volumes.Volumes) == 0 {
return nil, nil
}
return volumes.Volumes[0], nil
}
// getVolumeMountedIn gets the volume that is mounted in some container in some path
func getVolumeMountedIn(ID string, path string) (string, error) {
ctx := context.Background()
docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return "", fmt.Errorf(" Couldn't create docker client: %w", err)
}
c, err := docker.ContainerInspect(ctx, ID)
if err != nil {
return "", fmt.Errorf(" Couldn't inspect container %s: %w", ID, err)
}
for _, mount := range c.Mounts {
if mount.Destination == path {
return mount.Name, nil
}
}
return "", nil
}
// createImageVolume will create a new docker volume used for storing image tarballs that can be loaded into the clusters
func createImageVolume(clusterName string) (types.Volume, error) {
volName := fmt.Sprintf("k3d-%s-images", clusterName)
volLabels := map[string]string{
"app": "k3d",
"cluster": clusterName,
}
return createVolume(volName, volLabels)
}
// deleteImageVolume will delete the volume we created for sharing images with this cluster
func deleteImageVolume(clusterName string) error {
volName := fmt.Sprintf("k3d-%s-images", clusterName)
return deleteVolume(volName)
}
// getImageVolume returns the docker volume object representing the imagevolume for the cluster // getImageVolume returns the docker volume object representing the imagevolume for the cluster
func getImageVolume(clusterName string) (types.Volume, error) { func getImageVolume(clusterName string) (types.Volume, error) {
var vol types.Volume var vol types.Volume

View File

@ -136,6 +136,10 @@ func main() {
Value: defaultRegistryPort, Value: defaultRegistryPort,
Usage: "Port of the local registry container", Usage: "Port of the local registry container",
}, },
cli.StringFlag{
Name: "registry-volume",
Usage: "Use a specific volume for the registry storage (will be created if not existing)",
},
cli.StringFlag{ cli.StringFlag{
Name: "registries-file", Name: "registries-file",
Usage: "registries.yaml config file", Usage: "registries.yaml config file",
@ -219,6 +223,10 @@ func main() {
Name: "prune", Name: "prune",
Usage: "Disconnect any other non-k3d containers in the network before deleting the cluster", Usage: "Disconnect any other non-k3d containers in the network before deleting the cluster",
}, },
cli.BoolFlag{
Name: "keep-registry-volume",
Usage: "Do not delete the registry volume",
},
}, },
Action: run.DeleteCluster, Action: run.DeleteCluster,
}, },

View File

@ -33,7 +33,7 @@ else
fi fi
info "Creating two clusters (with a registry)..." info "Creating two clusters (with a registry)..."
$EXE create --wait 60 --name "c1" --api-port 6443 --enable-registry || failed "could not create cluster c1" $EXE create --wait 60 --name "c1" --api-port 6443 --enable-registry --registry-volume "reg-vol" || failed "could not create cluster c1"
$EXE create --wait 60 --name "c2" --api-port 6444 --enable-registry --registries-file "$REGISTRIES_YAML" || failed "could not create cluster c2" $EXE create --wait 60 --name "c2" --api-port 6444 --enable-registry --registries-file "$REGISTRIES_YAML" || failed "could not create cluster c2"
check_k3d_clusters "c1" "c2" || failed "error checking cluster" check_k3d_clusters "c1" "c2" || failed "error checking cluster"
@ -43,10 +43,14 @@ check_registry || abort "local registry not available at $REGISTRY"
passed "Local registry running at $REGISTRY" passed "Local registry running at $REGISTRY"
info "Deleting c1 cluster: the registry should remain..." info "Deleting c1 cluster: the registry should remain..."
$EXE delete --name "c1" || failed "could not delete the cluster c1" $EXE delete --name "c1" --keep-registry-volume || failed "could not delete the cluster c1"
check_registry || abort "local registry not available at $REGISTRY after removing c1" check_registry || abort "local registry not available at $REGISTRY after removing c1"
passed "The local registry is still running" passed "The local registry is still running"
info "Checking that the reg-vol still exists after removing c1"
check_volume_exists "reg-vol" || abort "the registry volume 'reg-vol' does not seem to exist"
passed "reg-vol still exists"
info "Pulling a test image..." info "Pulling a test image..."
docker pull $TEST_IMAGE docker pull $TEST_IMAGE
docker tag $TEST_IMAGE $REGISTRY/$TEST_IMAGE docker tag $TEST_IMAGE $REGISTRY/$TEST_IMAGE
@ -84,8 +88,18 @@ kubectl --kubeconfig=$($EXE get-kubeconfig --name "c2") wait --for=condition=ava
passed "Local registry seems to be usable" passed "Local registry seems to be usable"
info "Deleting c2 cluster: the registry should be removed now..." info "Deleting c2 cluster: the registry should be removed now..."
$EXE delete --name "c2" || failed "could not delete the cluster c2" $EXE delete --name "c2" --keep-registry-volume || failed "could not delete the cluster c2"
check_registry && abort "local registry still running at $REGISTRY" check_registry && abort "local registry still running at $REGISTRY"
passed "The local registry has been removed" passed "The local registry has been removed"
info "Creating a new clusters that uses a registry with an existsing 'reg-vol' volume..."
check_volume_exists "reg-vol" || abort "the registry volume 'reg-vol' does not exist"
$EXE create --wait 60 --name "c3" --api-port 6445 --enable-registry --registry-volume "reg-vol" || failed "could not create cluster c3"
info "Deleting c3 cluster: the registry should be removed and, this time, the volume too..."
$EXE delete --name "c3" || failed "could not delete the cluster c3"
check_volume_exists "reg-vol" && abort "the registry volume 'reg-vol' still exists"
passed "'reg-vol' has been removed"
exit 0 exit 0

View File

@ -83,3 +83,6 @@ check_registry() {
check_url $REGISTRY/v2/_catalog check_url $REGISTRY/v2/_catalog
} }
check_volume_exists() {
docker volume inspect "$1" >/dev/null 2>&1
}