Simplify how the Docker containers are handled.

This is done by introducting the concept of "namespaces" of container
names. The aliases of a container are under this namespace. Namespace
names are of the form:

//<namespace>/<alias>

This allows us to (within cAdvisor) query all docker containers as
//docker regardless of whether this is a systemd or a non-systemd system.

This does break our ability to handle Docker aliases with the /container
endpoint. I think this is acceptable as our support there was not
consistent between system types.
This commit is contained in:
Victor Marmol 2014-11-07 10:00:29 -08:00
parent fbafdbb860
commit f97e57df88
7 changed files with 111 additions and 72 deletions

View File

@ -31,6 +31,9 @@ import (
var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "docker endpoint")
// The namespace under which Docker aliases are unique.
var DockerNamespace = "docker"
// Basepath to all container specific information that libcontainer stores.
var dockerRootDir = flag.String("docker_root", "/var/lib/docker", "Absolute path to the Docker state root directory (default: /var/lib/docker)")
@ -54,7 +57,7 @@ type dockerFactory struct {
}
func (self *dockerFactory) String() string {
return "docker"
return DockerNamespace
}
func (self *dockerFactory) NewContainerHandler(name string) (handler container.ContainerHandler, err error) {
@ -102,19 +105,14 @@ func ContainerNameToDockerId(name string) string {
return id
}
// Returns a list of possible full container names for the specified Docker container name.
func FullDockerContainerNames(dockerName string) []string {
names := make([]string, 0, 2)
// Returns a full container name for the specified Docker ID.
func FullContainerName(dockerId string) string {
// Add the full container name.
if useSystemd {
names = append(names, path.Join("/system.slice", fmt.Sprintf("docker-%s.scope", dockerName)))
return path.Join("/system.slice", fmt.Sprintf("docker-%s.scope", dockerId))
} else {
names = append(names, path.Join("/docker", dockerName))
return path.Join("/docker", dockerId)
}
// Add the Docker alias name.
return append(names, path.Join("/docker", dockerName))
}
// Docker handles all containers under /docker

View File

@ -92,7 +92,11 @@ func newDockerContainerHandler(
if err != nil {
return nil, fmt.Errorf("failed to inspect container %s - %s\n", id, err)
}
handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name))
// Add the name and bare ID as aliases of the container.
handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/"))
handler.aliases = append(handler.aliases, id)
return handler, nil
}
@ -100,6 +104,7 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc
return info.ContainerReference{
Name: self.name,
Aliases: self.aliases,
Namespace: DockerNamespace,
}, nil
}
@ -297,12 +302,6 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType)
return nil, err
}
// On non-systemd systems Docker containers are under /docker.
containerPrefix := "/docker"
if useSystemd {
containerPrefix = "/system.slice"
}
ret := make([]info.ContainerReference, 0, len(containers)+1)
for _, c := range containers {
if !strings.HasPrefix(c.Status, "Up ") {
@ -310,8 +309,9 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType)
}
ref := info.ContainerReference{
Name: path.Join(containerPrefix, c.ID),
Aliases: c.Names,
Name: FullContainerName(c.ID),
Aliases: append(c.Names, c.ID),
Namespace: DockerNamespace,
}
ret = append(ret, ref)
}

View File

@ -53,10 +53,16 @@ type ContainerSpec struct {
// Container reference contains enough information to uniquely identify a container
type ContainerReference struct {
// The absolute name of the container.
// The absolute name of the container. This is unique on the machine.
Name string `json:"name"`
// Other names by which the container is known within a certain namespace.
// This is unique within that namespace.
Aliases []string `json:"aliases,omitempty"`
// Namespace under which the aliases of a container are unique.
// An example of a namespace is "docker" for Docker containers.
Namespace string `json:"namespace,omitempty"`
}
// ContainerInfoQuery is used when users check a container info from the REST api.

View File

@ -104,8 +104,7 @@ func newContainerData(containerName string, driver storage.StorageDriver, handle
logUsage: logUsage,
stop: make(chan bool, 1),
}
cont.info.Name = ref.Name
cont.info.Aliases = ref.Aliases
cont.info.ContainerReference = ref
return cont, nil
}

View File

@ -75,7 +75,7 @@ func New(driver storage.StorageDriver) (Manager, error) {
glog.Infof("cAdvisor running in container: %q", selfContainer)
newManager := &manager{
containers: make(map[string]*containerData),
containers: make(map[namespacedContainerName]*containerData),
quitChannels: make([]chan error, 0, 2),
storageDriver: driver,
cadvisorContainer: selfContainer,
@ -99,8 +99,17 @@ func New(driver storage.StorageDriver) (Manager, error) {
return newManager, nil
}
// A namespaced container name.
type namespacedContainerName struct {
// The namespace of the container. Can be empty for the root namespace.
Namespace string
// The name of the container in this namespace.
Name string
}
type manager struct {
containers map[string]*containerData
containers map[namespacedContainerName]*containerData
containersLock sync.RWMutex
storageDriver storage.StorageDriver
machineInfo info.MachineInfo
@ -198,7 +207,9 @@ func (self *manager) GetContainerInfo(containerName string, query *info.Containe
defer self.containersLock.RUnlock()
// Ensure we have the container.
cont, ok = self.containers[containerName]
cont, ok = self.containers[namespacedContainerName{
Name: containerName,
}]
}()
if !ok {
return nil, fmt.Errorf("unknown container %q", containerName)
@ -221,10 +232,7 @@ func (self *manager) containerDataToContainerInfo(cont *containerData, query *in
// Make a copy of the info for the user.
ret := &info.ContainerInfo{
ContainerReference: info.ContainerReference{
Name: cinfo.Name,
Aliases: cinfo.Aliases,
},
ContainerReference: cinfo.ContainerReference,
Subcontainers: cinfo.Subcontainers,
Spec: cinfo.Spec,
Stats: stats,
@ -275,20 +283,15 @@ func (self *manager) DockerContainersInfo(containerName string, query *info.Cont
}
}
} else {
// Strip first "/"
containerName = strings.Trim(containerName, "/")
// Get the specified container (check all possible Docker names).
possibleNames := docker.FullDockerContainerNames(containerName)
for _, fullName := range possibleNames {
cont, ok := self.containers[fullName]
// Check for the container in the Docker container namespace.
cont, ok := self.containers[namespacedContainerName{
Namespace: docker.DockerNamespace,
Name: containerName,
}]
if ok {
containers = append(containers, cont)
break
}
}
if len(containers) == 0 {
return fmt.Errorf("unable to find Docker container %q with full names %v", containerName, possibleNames)
} else {
return fmt.Errorf("unable to find Docker container %q", containerName)
}
}
return nil
@ -345,16 +348,23 @@ func (m *manager) createContainer(containerName string) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
namespacedName := namespacedContainerName{
Name: containerName,
}
// Check that the container didn't already exist.
_, ok := m.containers[containerName]
_, ok := m.containers[namespacedName]
if ok {
return true
}
// Add the container name and all its aliases.
m.containers[containerName] = cont
// Add the container name and all its aliases. The aliases must be within the namespace of the factory.
m.containers[namespacedName] = cont
for _, alias := range cont.info.Aliases {
m.containers[alias] = cont
m.containers[namespacedContainerName{
Namespace: cont.info.Namespace,
Name: alias,
}] = cont
}
return false
@ -362,7 +372,7 @@ func (m *manager) createContainer(containerName string) error {
if alreadyExists {
return nil
}
glog.Infof("Added container: %q (aliases: %s)", containerName, cont.info.Aliases)
glog.Infof("Added container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
// Start the container's housekeeping.
cont.Start()
@ -373,7 +383,10 @@ func (m *manager) destroyContainer(containerName string) error {
m.containersLock.Lock()
defer m.containersLock.Unlock()
cont, ok := m.containers[containerName]
namespacedName := namespacedContainerName{
Name: containerName,
}
cont, ok := m.containers[namespacedName]
if !ok {
// Already destroyed, done.
return nil
@ -386,11 +399,14 @@ func (m *manager) destroyContainer(containerName string) error {
}
// Remove the container from our records (and all its aliases).
delete(m.containers, containerName)
delete(m.containers, namespacedName)
for _, alias := range cont.info.Aliases {
delete(m.containers, alias)
delete(m.containers, namespacedContainerName{
Namespace: cont.info.Namespace,
Name: alias,
})
}
glog.Infof("Destroyed container: %s (aliases: %s)", containerName, cont.info.Aliases)
glog.Infof("Destroyed container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
return nil
}
@ -400,7 +416,9 @@ func (m *manager) getContainersDiff(containerName string) (added []info.Containe
defer m.containersLock.RUnlock()
// Get all subcontainers recursively.
cont, ok := m.containers[containerName]
cont, ok := m.containers[namespacedContainerName{
Name: containerName,
}]
if !ok {
return nil, nil, fmt.Errorf("failed to find container %q while checking for new containers", containerName)
}
@ -414,15 +432,17 @@ func (m *manager) getContainersDiff(containerName string) (added []info.Containe
allContainersSet := make(map[string]*containerData)
for name, d := range m.containers {
// Only add the canonical name.
if d.info.Name == name {
allContainersSet[name] = d
if d.info.Name == name.Name {
allContainersSet[name.Name] = d
}
}
// Added containers
for _, c := range allContainers {
delete(allContainersSet, c.Name)
_, ok := m.containers[c.Name]
_, ok := m.containers[namespacedContainerName{
Name: c.Name,
}]
if !ok {
added = append(added, c)
}
@ -474,7 +494,9 @@ func (self *manager) watchForNewContainers(quit chan error) error {
func() {
self.containersLock.RLock()
defer self.containersLock.RUnlock()
root, ok = self.containers["/"]
root, ok = self.containers[namespacedContainerName{
Name: "/",
}]
}()
if !ok {
return fmt.Errorf("Root container does not exist when watching for new containers")

View File

@ -18,15 +18,19 @@ package manager
import (
"reflect"
"strings"
"testing"
"time"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/info"
itest "github.com/google/cadvisor/info/test"
stest "github.com/google/cadvisor/storage/test"
)
// TODO(vmarmol): Refactor these tests.
func createManagerAndAddContainers(
driver *stest.MockStorageDriver,
containers []string,
@ -44,10 +48,20 @@ func createManagerAndAddContainers(
if ret, ok := mif.(*manager); ok {
for _, name := range containers {
mockHandler := container.NewMockContainerHandler(name)
ret.containers[name], err = newContainerData(name, driver, mockHandler, false)
cont, err := newContainerData(name, driver, mockHandler, false)
if err != nil {
t.Fatal(err)
}
ret.containers[namespacedContainerName{
Name: name,
}] = cont
// Add Docker containers under their namespace.
if strings.HasPrefix(name, "/docker") {
ret.containers[namespacedContainerName{
Namespace: docker.DockerNamespace,
Name: strings.TrimPrefix(name, "/docker/"),
}] = cont
}
f(mockHandler)
}
return ret

View File

@ -19,7 +19,7 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error
start := time.Now()
// The container name is the path after the handler
containerName := u.Path[len(DockerPage)-1:]
containerName := u.Path[len(DockerPage):]
var data *pageData
if containerName == "/" {