-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial podman support #2794
Initial podman support #2794
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Copyright 2021 Google Inc. All Rights Reserved. | ||
// | ||
// 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 podman | ||
|
||
import ( | ||
"net/http" | ||
"sync" | ||
|
||
dclient "github.com/docker/docker/client" | ||
"github.com/docker/go-connections/tlsconfig" | ||
) | ||
|
||
var ( | ||
podmanClient *dclient.Client | ||
podmanClientErr error | ||
podmanClientOnce sync.Once | ||
) | ||
|
||
// NewClient creates a Docker API client based on the given Podman flags | ||
// | ||
// At this time, we're using the podmans docker compatibility API layer | ||
// for podman containers. | ||
func NewClient() (*dclient.Client, error) { | ||
podmanClientOnce.Do(func() { | ||
var client *http.Client | ||
if *ArgPodmanTLS { | ||
client = &http.Client{} | ||
options := tlsconfig.Options{ | ||
CAFile: *ArgPodmanCA, | ||
CertFile: *ArgPodmanCert, | ||
KeyFile: *ArgPodmanKey, | ||
InsecureSkipVerify: false, | ||
} | ||
tlsc, err := tlsconfig.Client(options) | ||
if err != nil { | ||
podmanClientErr = err | ||
return | ||
} | ||
client.Transport = &http.Transport{ | ||
TLSClientConfig: tlsc, | ||
} | ||
} | ||
podmanClient, podmanClientErr = dclient.NewClientWithOpts( | ||
dclient.WithHost(*ArgPodmanEndpoint), | ||
dclient.WithHTTPClient(client), | ||
dclient.WithAPIVersionNegotiation()) | ||
}) | ||
return podmanClient, podmanClientErr | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
// Copyright 2021 Google Inc. All Rights Reserved. | ||
// | ||
// 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 podman | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"path" | ||
"regexp" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/google/cadvisor/container" | ||
"github.com/google/cadvisor/container/libcontainer" | ||
"github.com/google/cadvisor/fs" | ||
info "github.com/google/cadvisor/info/v1" | ||
"github.com/google/cadvisor/watcher" | ||
|
||
docker "github.com/docker/docker/client" | ||
"golang.org/x/net/context" | ||
"k8s.io/klog/v2" | ||
) | ||
|
||
const ( | ||
// The namespace under which podman aliases are unique. | ||
PodmanNamespace = "podman" | ||
|
||
// The retry times for getting podman root dir | ||
rootDirRetries = 5 | ||
|
||
// The retry period for getting podman root dir, Millisecond | ||
rootDirRetryPeriod time.Duration = 1000 * time.Millisecond | ||
) | ||
|
||
var ( | ||
ArgPodmanEndpoint = flag.String("podman", "unix:///run/podman/podman.sock", "podman endpoint") | ||
ArgPodmanTLS = flag.Bool("podman-tls", false, "use TLS to connect to podman") | ||
ArgPodmanCert = flag.String("podman-tls-cert", "cert.pem", "path to client certificate") | ||
ArgPodmanKey = flag.String("podman-tls-key", "key.pem", "path to private key") | ||
ArgPodmanCA = flag.String("podman-tls-ca", "ca.pem", "path to trusted CA") | ||
|
||
// Regexp that identifies podman cgroups | ||
// --cgroup-parent have another prefix than 'libpod' | ||
podmanCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`) | ||
|
||
podmanEnvWhitelist = flag.String("podman_env_metadata_whitelist", "", "a comma-separated list of environment variable keys matched with specified prefix that needs to be collected for podman containers") | ||
// Basepath to all container specific information that libcontainer stores. | ||
podmanRootDir string | ||
|
||
podmanRootDirOnce sync.Once | ||
) | ||
|
||
func RootDir() string { | ||
podmanRootDirOnce.Do(func() { | ||
for i := 0; i < rootDirRetries; i++ { | ||
status, err := Status() | ||
if err == nil && status.RootDir != "" { | ||
podmanRootDir = status.RootDir | ||
break | ||
} else { | ||
time.Sleep(rootDirRetryPeriod) | ||
} | ||
} | ||
}) | ||
return podmanRootDir | ||
} | ||
|
||
type podmanFactory struct { | ||
machineInfoFactory info.MachineInfoFactory | ||
|
||
client *docker.Client | ||
|
||
// Information about the mounted cgroup subsystems. | ||
cgroupSubsystems libcontainer.CgroupSubsystems | ||
|
||
// Information about mounted filesystems. | ||
fsInfo fs.FsInfo | ||
|
||
podmanVersion []int | ||
|
||
podmanAPIVersion []int | ||
|
||
includedMetrics container.MetricSet | ||
} | ||
|
||
func (f *podmanFactory) String() string { | ||
return PodmanNamespace | ||
} | ||
|
||
func (f *podmanFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) { | ||
client, err := NewClient() | ||
if err != nil { | ||
return | ||
} | ||
|
||
metadataEnvs := strings.Split(*podmanEnvWhitelist, ",") | ||
|
||
handler, err = newPodmanContainerHandler( | ||
client, | ||
name, | ||
f.machineInfoFactory, | ||
f.fsInfo, | ||
&f.cgroupSubsystems, | ||
inHostNamespace, | ||
metadataEnvs, | ||
f.podmanVersion, | ||
f.includedMetrics, | ||
) | ||
return | ||
} | ||
|
||
// Returns the Podman ID from the full container name. | ||
func CgroupNameToPodmanId(name string) string { | ||
id := path.Base(name) | ||
|
||
if matches := podmanCgroupRegexp.FindStringSubmatch(id); matches != nil { | ||
return matches[1] | ||
} | ||
|
||
return id | ||
} | ||
|
||
// isContainerName returns true if the cgroup with associated name | ||
// could be a podman container. | ||
// the actual decision is made by running a ContainerInspect API call | ||
func isContainerName(name string) bool { | ||
towe75 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// always ignore .mount cgroup even if associated with podman and delegate to systemd | ||
if strings.HasSuffix(name, ".mount") { | ||
return false | ||
} | ||
return podmanCgroupRegexp.MatchString(path.Base(name)) | ||
} | ||
|
||
// Podman handles all containers prefixed with libpod- | ||
func (f *podmanFactory) CanHandleAndAccept(name string) (bool, bool, error) { | ||
// if the container is not associated with podman, we can't handle it or accept it. | ||
if !isContainerName(name) { | ||
return false, false, nil | ||
} | ||
|
||
// Check if the container is known to podman and it is active. | ||
id := CgroupNameToPodmanId(name) | ||
|
||
// We assume that if Inspect fails then the container is not known to podman. | ||
ctnr, err := f.client.ContainerInspect(context.Background(), id) | ||
if err != nil || !ctnr.State.Running { | ||
return false, true, fmt.Errorf("error inspecting container: %v", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are two potentially different scenarios:
Can you return more specific error, please (hint: |
||
} | ||
|
||
return true, true, nil | ||
} | ||
|
||
func (f *podmanFactory) DebugInfo() map[string][]string { | ||
return map[string][]string{} | ||
} | ||
|
||
var ( | ||
versionRegexpString = `(\d+)\.(\d+)\.(\d+)` | ||
versionRe = regexp.MustCompile(versionRegexpString) | ||
apiVersionRegexpString = `(\d+)\.(\d+)` | ||
apiVersionRe = regexp.MustCompile(apiVersionRegexpString) | ||
) | ||
|
||
Comment on lines
+170
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if it is necessary to rely on regular expressions to parse versions. As far as I understand we expect them to be strings received from Podman API so perhaps splitting these strings an calling |
||
// Register root container before running this function! | ||
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error { | ||
client, err := NewClient() | ||
if err != nil { | ||
return fmt.Errorf("unable to communicate with podman: %v", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider wrapping the error. |
||
} | ||
|
||
podmanInfo, err := ValidateInfo() | ||
if err != nil { | ||
return fmt.Errorf("failed to validate Podman info: %v", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
} | ||
|
||
// Version already validated above, assume no error here. | ||
podmanVersion, _ := parseVersion(podmanInfo.ServerVersion, versionRe, 3) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the version is already validated then why don't you just split a string? Regular expression might be an overkill in this scenario. |
||
|
||
podmanAPIVersion, _ := APIVersion() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why have you decided to ignore the error? You can assume this call will never fail. |
||
|
||
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics) | ||
if err != nil { | ||
return fmt.Errorf("failed to get cgroup subsystems: %v", err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap an error, please. |
||
} | ||
|
||
klog.V(1).Infof("Registering Podman factory") | ||
f := &podmanFactory{ | ||
cgroupSubsystems: cgroupSubsystems, | ||
client: client, | ||
podmanVersion: podmanVersion, | ||
podmanAPIVersion: podmanAPIVersion, | ||
fsInfo: fsInfo, | ||
machineInfoFactory: factory, | ||
includedMetrics: includedMetrics, | ||
} | ||
|
||
container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw}) | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expected some context to be added before returning the error. I'm not sure if using named returned valued does not decrease code readability.