mirror of
https://github.com/containers/podman.git
synced 2025-05-20 16:47:39 +08:00
kube: Add support for podman pod logs
Following PR adds support for `kubectl` like `pod logs` to podman. Usage `podman pod logs <podIDorName` gives a stream of logs for all the containers within the pod with **containername** as a field. Just like **`kubectl`** also supports `podman pod logs -c ctrIDorName podIDorName` to limit the log stream to any of the specificied container which belongs to pod. Signed-off-by: Aditya Rajan <arajan@redhat.com>
This commit is contained in:
140
cmd/podman/pods/logs.go
Normal file
140
cmd/podman/pods/logs.go
Normal file
@ -0,0 +1,140 @@
|
||||
package pods
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking
|
||||
// CLI-only fields into the API types.
|
||||
type logsOptionsWrapper struct {
|
||||
entities.PodLogsOptions
|
||||
|
||||
SinceRaw string
|
||||
|
||||
UntilRaw string
|
||||
}
|
||||
|
||||
var (
|
||||
logsPodOptions logsOptionsWrapper
|
||||
logsPodDescription = `Displays logs for pod with one or more containers.`
|
||||
logsPodCommand = &cobra.Command{
|
||||
Use: "logs [options] POD",
|
||||
Short: "Fetch logs for pod with one or more containers",
|
||||
Long: logsPodDescription,
|
||||
// We dont want users to invoke latest and pod togather
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
switch {
|
||||
case registry.IsRemote() && logsPodOptions.Latest:
|
||||
return errors.New(cmd.Name() + " does not support 'latest' when run remotely")
|
||||
case len(args) > 1:
|
||||
return errors.New("requires exactly 1 arg")
|
||||
case logsPodOptions.Latest && len(args) > 0:
|
||||
return errors.New("--latest and pods cannot be used together")
|
||||
case !logsPodOptions.Latest && len(args) < 1:
|
||||
return errors.New("specify at least one pod name or ID to log")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: logs,
|
||||
ValidArgsFunction: common.AutocompletePods,
|
||||
Example: `podman pod logs podID
|
||||
podman pod logs -c ctrname podName
|
||||
podman pod logs --tail 2 mywebserver
|
||||
podman pod logs --follow=true --since 10m podID
|
||||
podman pod logs mywebserver`,
|
||||
}
|
||||
|
||||
containerLogsCommand = &cobra.Command{
|
||||
Use: logsPodCommand.Use,
|
||||
Short: logsPodCommand.Short,
|
||||
Long: logsPodCommand.Long,
|
||||
Args: logsPodCommand.Args,
|
||||
RunE: logsPodCommand.RunE,
|
||||
ValidArgsFunction: logsPodCommand.ValidArgsFunction,
|
||||
Example: `podman pod logs podId
|
||||
podman pod logs -c ctrname podName
|
||||
podman pod logs --tail 2 mywebserver
|
||||
podman pod logs --follow=true --since 10m podID`,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Command: logsPodCommand,
|
||||
})
|
||||
logsFlags(logsPodCommand)
|
||||
validate.AddLatestFlag(logsPodCommand, &logsPodOptions.Latest)
|
||||
|
||||
// container logs
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Command: containerLogsCommand,
|
||||
Parent: podCmd,
|
||||
})
|
||||
logsFlags(containerLogsCommand)
|
||||
validate.AddLatestFlag(containerLogsCommand, &logsPodOptions.Latest)
|
||||
}
|
||||
|
||||
func logsFlags(cmd *cobra.Command) {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVar(&logsPodOptions.Details, "details", false, "Show extra details provided to the logs")
|
||||
flags.BoolVarP(&logsPodOptions.Follow, "follow", "f", false, "Follow log output.")
|
||||
|
||||
containerNameFlag := "container"
|
||||
flags.StringVarP(&logsPodOptions.ContainerName, containerNameFlag, "c", "", "Filter logs by container name or id which belongs to pod")
|
||||
_ = cmd.RegisterFlagCompletionFunc(containerNameFlag, common.AutocompleteContainers)
|
||||
|
||||
sinceFlagName := "since"
|
||||
flags.StringVar(&logsPodOptions.SinceRaw, sinceFlagName, "", "Show logs since TIMESTAMP")
|
||||
_ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone)
|
||||
|
||||
untilFlagName := "until"
|
||||
flags.StringVar(&logsPodOptions.UntilRaw, untilFlagName, "", "Show logs until TIMESTAMP")
|
||||
_ = cmd.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone)
|
||||
|
||||
tailFlagName := "tail"
|
||||
flags.Int64Var(&logsPodOptions.Tail, tailFlagName, -1, "Output the specified number of LINES at the end of the logs.")
|
||||
_ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone)
|
||||
|
||||
flags.BoolVarP(&logsPodOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
|
||||
flags.SetInterspersed(false)
|
||||
_ = flags.MarkHidden("details")
|
||||
}
|
||||
|
||||
func logs(_ *cobra.Command, args []string) error {
|
||||
if logsPodOptions.SinceRaw != "" {
|
||||
// parse time, error out if something is wrong
|
||||
since, err := util.ParseInputTime(logsPodOptions.SinceRaw, true)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing --since %q", logsPodOptions.SinceRaw)
|
||||
}
|
||||
logsPodOptions.Since = since
|
||||
}
|
||||
if logsPodOptions.UntilRaw != "" {
|
||||
// parse time, error out if something is wrong
|
||||
until, err := util.ParseInputTime(logsPodOptions.UntilRaw, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing --until %q", logsPodOptions.UntilRaw)
|
||||
}
|
||||
logsPodOptions.Until = until
|
||||
}
|
||||
|
||||
// Remote can only process one container at a time
|
||||
if registry.IsRemote() && logsPodOptions.ContainerName == "" {
|
||||
return errors.Wrapf(define.ErrInvalidArg, "-c or --container cannot be empty")
|
||||
}
|
||||
|
||||
logsPodOptions.StdoutWriter = os.Stdout
|
||||
logsPodOptions.StderrWriter = os.Stderr
|
||||
return registry.ContainerEngine().PodLogs(registry.GetContext(), args[0], logsPodOptions.PodLogsOptions)
|
||||
}
|
88
docs/source/markdown/podman-pod-logs.1.md
Normal file
88
docs/source/markdown/podman-pod-logs.1.md
Normal file
@ -0,0 +1,88 @@
|
||||
% podman-pod-logs(1)
|
||||
|
||||
## NAME
|
||||
podman\-pod\-logs - Displays logs for pod with one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**podman pod logs** [*options*] *pod*
|
||||
|
||||
## DESCRIPTION
|
||||
The podman pod logs command batch-retrieves whatever logs are present with all the containers of a pod. Pod logs can be filtered by container name or id using flag **-c** or **--container** if needed.
|
||||
|
||||
Note: Long running command of `podman pod log` with a `-f` or `--follow` needs to be reinvoked if new container is added to the pod dynamically otherwise logs of newly added containers would not be visible in log stream.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--container**, **-c**
|
||||
|
||||
By default `podman pod logs` retrives logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`.
|
||||
|
||||
#### **--follow**, **-f**
|
||||
|
||||
Follow log output. Default is false.
|
||||
|
||||
Note: If you are following a pod which is removed `podman pod rm`, then there is a
|
||||
chance the the log file will be removed before `podman pod logs` reads the final content.
|
||||
|
||||
#### **--latest**, **-l**
|
||||
|
||||
Instead of providing the pod name or id, get logs of the last created pod. (This option is not available with the remote Podman client)
|
||||
|
||||
#### **--since**=*TIMESTAMP*
|
||||
|
||||
Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
|
||||
strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
|
||||
time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
|
||||
and 2006-01-02.
|
||||
|
||||
#### **--until**=*TIMESTAMP*
|
||||
|
||||
Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration
|
||||
strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
|
||||
time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
|
||||
and 2006-01-02.
|
||||
|
||||
|
||||
#### **--tail**=*LINES*
|
||||
|
||||
Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1,
|
||||
which prints all lines
|
||||
|
||||
#### **--timestamps**, **-t**
|
||||
|
||||
Show timestamps in the log outputs. The default is false
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
To view a pod's logs:
|
||||
```
|
||||
podman pod logs -t podIdorName
|
||||
```
|
||||
|
||||
To view logs of a specific container on the pod
|
||||
```
|
||||
podman pod logs -c ctrIdOrName podIdOrName
|
||||
```
|
||||
|
||||
To view all pod logs:
|
||||
```
|
||||
podman pod logs -t --since 0 myserver-pod-1
|
||||
```
|
||||
|
||||
To view a pod's logs since a certain time:
|
||||
```
|
||||
podman pod logs -t --since 2017-08-07T10:10:09.055837383-04:00 myserver-pod-1
|
||||
```
|
||||
|
||||
To view a pod's logs generated in the last 10 minutes:
|
||||
```
|
||||
podman pod logs --since 10m myserver-pod-1
|
||||
```
|
||||
|
||||
To view a pod's logs until 30 minutes ago:
|
||||
```
|
||||
podman pod logs --until 30m myserver-pod-1
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman(1), podman-pod-start(1), podman-pod-rm(1), podman-logs(1)
|
@ -17,11 +17,12 @@ podman pod is a set of subcommands that manage pods, or groups of containers.
|
||||
| exists | [podman-pod-exists(1)](podman-pod-exists.1.md) | Check if a pod exists in local storage. |
|
||||
| inspect | [podman-pod-inspect(1)](podman-pod-inspect.1.md) | Displays information describing a pod. |
|
||||
| kill | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in one or more pods. |
|
||||
| logs | [podman-pod-logs(1)](podman-pod-logs.1.md) | Displays logs for pod with one or more containers. |
|
||||
| pause | [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. |
|
||||
| prune | [podman-pod-prune(1)](podman-pod-prune.1.md) | Remove all stopped pods and their containers. |
|
||||
| prune | [podman-pod-prune(1)](podman-pod-prune.1.md) | Remove all stopped pods and their containers. |
|
||||
| ps | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. |
|
||||
| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. |
|
||||
| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more stopped pods and containers. |
|
||||
| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more stopped pods and containers. |
|
||||
| start | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. |
|
||||
| stats | [podman-pod-stats(1)](podman-pod-stats.1.md) | Display a live stream of resource usage stats for containers in one or more pods. |
|
||||
| stop | [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more pods. |
|
||||
|
@ -9,6 +9,8 @@ Pod
|
||||
|
||||
:doc:`kill <markdown/podman-pod-kill.1>` Send the specified signal or SIGKILL to containers in pod
|
||||
|
||||
:doc:`logs <markdown/podman-pod-logs.1>` Displays logs for pod with one or more containers
|
||||
|
||||
:doc:`pause <markdown/podman-pause.1>` Pause one or more pods
|
||||
|
||||
:doc:`prune <markdown/podman-pod-prune.1>` Remove all stopped pods and their containers
|
||||
|
@ -72,6 +72,7 @@ type ContainerEngine interface {
|
||||
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
|
||||
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
|
||||
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
|
||||
PodLogs(ctx context.Context, pod string, options PodLogsOptions) error
|
||||
PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error)
|
||||
PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error)
|
||||
PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error)
|
||||
|
@ -133,6 +133,14 @@ type PodCreateOptions struct {
|
||||
Userns specgen.Namespace
|
||||
}
|
||||
|
||||
// PodLogsOptions describes the options to extract pod logs.
|
||||
type PodLogsOptions struct {
|
||||
// Other fields are exactly same as ContainerLogOpts
|
||||
ContainerLogsOptions
|
||||
// If specified will only fetch the logs of specified container
|
||||
ContainerName string
|
||||
}
|
||||
|
||||
type ContainerCreateOptions struct {
|
||||
Annotation []string
|
||||
Attach []string
|
||||
@ -426,3 +434,22 @@ func ValidatePodStatsOptions(args []string, options *PodStatsOptions) error {
|
||||
return errors.New("--all, --latest and arguments cannot be used together")
|
||||
}
|
||||
}
|
||||
|
||||
// Converts PodLogOptions to ContainerLogOptions
|
||||
func PodLogsOptionsToContainerLogsOptions(options PodLogsOptions) ContainerLogsOptions {
|
||||
// PodLogsOptions are similar but contains few extra fields like ctrName
|
||||
// So cast other values as is so we can re-use the code
|
||||
containerLogsOpts := ContainerLogsOptions{
|
||||
Details: options.Details,
|
||||
Latest: options.Latest,
|
||||
Follow: options.Follow,
|
||||
Names: options.Names,
|
||||
Since: options.Since,
|
||||
Until: options.Until,
|
||||
Tail: options.Tail,
|
||||
Timestamps: options.Timestamps,
|
||||
StdoutWriter: options.StdoutWriter,
|
||||
StderrWriter: options.StderrWriter,
|
||||
}
|
||||
return containerLogsOpts
|
||||
}
|
||||
|
@ -83,6 +83,46 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodLogs(ctx context.Context, nameOrID string, options entities.PodLogsOptions) error {
|
||||
// Implementation accepts slice
|
||||
podName := []string{nameOrID}
|
||||
pod, err := getPodsByContext(false, options.Latest, podName, ic.Libpod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get pod containers
|
||||
podCtrs, err := pod[0].AllContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrNames := []string{}
|
||||
// Check if `kubectl pod logs -c ctrname <podname>` alike command is used
|
||||
if options.ContainerName != "" {
|
||||
ctrFound := false
|
||||
for _, ctr := range podCtrs {
|
||||
if ctr.ID() == options.ContainerName || ctr.Name() == options.ContainerName {
|
||||
ctrNames = append(ctrNames, options.ContainerName)
|
||||
ctrFound = true
|
||||
}
|
||||
}
|
||||
if !ctrFound {
|
||||
return errors.Wrapf(define.ErrNoSuchCtr, "container %s is not in pod %s", options.ContainerName, nameOrID)
|
||||
}
|
||||
} else {
|
||||
// No container name specified select all containers
|
||||
for _, ctr := range podCtrs {
|
||||
ctrNames = append(ctrNames, ctr.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PodLogsOptions are similar but contains few extra fields like ctrName
|
||||
// So cast other values as is so we can re-use the code
|
||||
containerLogsOpts := entities.PodLogsOptionsToContainerLogsOptions(options)
|
||||
|
||||
return ic.ContainerLogs(ctx, ctrNames, containerLogsOpts)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) {
|
||||
reports := []*entities.PodPauseReport{}
|
||||
pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
|
||||
|
@ -42,6 +42,16 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodLogs(_ context.Context, nameOrIDs string, options entities.PodLogsOptions) error {
|
||||
// PodLogsOptions are similar but contains few extra fields like ctrName
|
||||
// So cast other values as is so we can re-use the code
|
||||
containerLogsOpts := entities.PodLogsOptionsToContainerLogsOptions(options)
|
||||
|
||||
// interface only accepts slice, keep everything consistent
|
||||
name := []string{options.ContainerName}
|
||||
return ic.ContainerLogs(nil, name, containerLogsOpts)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) {
|
||||
foundPods, err := getPodsByContext(ic.ClientCtx, options.All, namesOrIds)
|
||||
if err != nil {
|
||||
|
@ -1289,6 +1289,40 @@ var _ = Describe("Podman play kube", func() {
|
||||
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
|
||||
})
|
||||
|
||||
It("podman pod logs test", func() {
|
||||
SkipIfRemote("podman-remote pod logs -c is mandatory for remote machine")
|
||||
p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
|
||||
|
||||
err := generateKubeYaml("pod", p, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
|
||||
logs := podmanTest.Podman([]string{"pod", "logs", p.Name})
|
||||
logs.WaitWithDefaultTimeout()
|
||||
Expect(logs).Should(Exit(0))
|
||||
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
|
||||
})
|
||||
|
||||
It("podman-remote pod logs test", func() {
|
||||
// -c or --container is required in podman-remote due to api limitation.
|
||||
p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
|
||||
|
||||
err := generateKubeYaml("pod", p, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube).Should(Exit(0))
|
||||
|
||||
logs := podmanTest.Podman([]string{"pod", "logs", "-c", getCtrNameInPod(p), p.Name})
|
||||
logs.WaitWithDefaultTimeout()
|
||||
Expect(logs).Should(Exit(0))
|
||||
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
|
||||
})
|
||||
|
||||
It("podman play kube test restartPolicy", func() {
|
||||
// podName, set, expect
|
||||
testSli := [][]string{
|
||||
|
Reference in New Issue
Block a user