
Closes #603 This adds logging facilities at the shim level to provide minimal I/O overhead and pluggable logging options. Log handling is done within the shim so that all I/O, cpu, and memory can be charged to the container. A sample logging driver setting up logging for a container the systemd journal looks like this: ```go package main import ( "bufio" "context" "fmt" "io" "sync" "github.com/containerd/containerd/runtime/v2/logging" "github.com/coreos/go-systemd/journal" ) func main() { logging.Run(log) } func log(ctx context.Context, config *logging.Config, ready func() error) error { // construct any log metadata for the container vars := map[string]string{ "SYSLOG_IDENTIFIER": fmt.Sprintf("%s:%s", config.Namespace, config.ID), } var wg sync.WaitGroup wg.Add(2) // forward both stdout and stderr to the journal go copy(&wg, config.Stdout, journal.PriInfo, vars) go copy(&wg, config.Stderr, journal.PriErr, vars) // signal that we are ready and setup for the container to be started if err := ready(); err != nil { return err } wg.Wait() return nil } func copy(wg *sync.WaitGroup, r io.Reader, pri journal.Priority, vars map[string]string) { defer wg.Done() s := bufio.NewScanner(r) for s.Scan() { if s.Err() != nil { return } journal.Send(s.Text(), pri, vars) } } ``` A `logging` package has been created to assist log developers create logging plugins for containerd. This uses a URI based approach for logging drivers that can be expanded in the future. Supported URI scheme's are: * binary * fifo * file You can pass the log url via ctr on the command line: ```bash > ctr run --rm --runtime io.containerd.runc.v2 --log-uri binary://shim-journald docker.io/library/redis:alpine redis ``` ```bash > journalctl -f -t default:redis -- Logs begin at Tue 2018-12-11 16:29:51 EST. -- Mar 08 16:08:22 deathstar default:redis[120760]: 1:C 08 Mar 2019 21:08:22.703 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.704 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.704 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.704 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 * Running mode=standalone, port=6379. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 # Server initialized Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. Mar 08 16:08:22 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:22.705 * Ready to accept connections Mar 08 16:08:50 deathstar default:redis[120760]: 1:signal-handler (1552079330) Received SIGINT scheduling shutdown... Mar 08 16:08:50 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:50.405 # User requested shutdown... Mar 08 16:08:50 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:50.406 * Saving the final RDB snapshot before exiting. Mar 08 16:08:50 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:50.452 * DB saved on disk Mar 08 16:08:50 deathstar default:redis[120760]: 1:M 08 Mar 2019 21:08:50.453 # Redis is now ready to exit, bye bye... ``` The following client side Opts are added: ```go // LogURI provides the raw logging URI func LogURI(uri *url.URL) Creator { } // BinaryIO forwards contianer STDOUT|STDERR directly to a logging binary func BinaryIO(binary string, args map[string]string) Creator {} ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
410 lines
11 KiB
Go
410 lines
11 KiB
Go
// +build !windows
|
|
|
|
/*
|
|
Copyright The containerd Authors.
|
|
|
|
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 proc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/containerd/console"
|
|
"github.com/containerd/containerd/runtime/proc"
|
|
runc "github.com/containerd/go-runc"
|
|
google_protobuf "github.com/gogo/protobuf/types"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type initState interface {
|
|
Resize(console.WinSize) error
|
|
Start(context.Context) error
|
|
Delete(context.Context) error
|
|
Pause(context.Context) error
|
|
Resume(context.Context) error
|
|
Update(context.Context, *google_protobuf.Any) error
|
|
Checkpoint(context.Context, *CheckpointConfig) error
|
|
Exec(context.Context, string, *ExecConfig) (proc.Process, error)
|
|
Kill(context.Context, uint32, bool) error
|
|
SetExited(int)
|
|
}
|
|
|
|
type createdState struct {
|
|
p *Init
|
|
}
|
|
|
|
func (s *createdState) transition(name string) error {
|
|
switch name {
|
|
case "running":
|
|
s.p.initState = &runningState{p: s.p}
|
|
case "stopped":
|
|
s.p.initState = &stoppedState{p: s.p}
|
|
case "deleted":
|
|
s.p.initState = &deletedState{}
|
|
default:
|
|
return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *createdState) Pause(ctx context.Context) error {
|
|
return errors.Errorf("cannot pause task in created state")
|
|
}
|
|
|
|
func (s *createdState) Resume(ctx context.Context) error {
|
|
return errors.Errorf("cannot resume task in created state")
|
|
}
|
|
|
|
func (s *createdState) Update(ctx context.Context, r *google_protobuf.Any) error {
|
|
return s.p.update(ctx, r)
|
|
}
|
|
|
|
func (s *createdState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
|
|
return errors.Errorf("cannot checkpoint a task in created state")
|
|
}
|
|
|
|
func (s *createdState) Resize(ws console.WinSize) error {
|
|
return s.p.resize(ws)
|
|
}
|
|
|
|
func (s *createdState) Start(ctx context.Context) error {
|
|
if err := s.p.start(ctx); err != nil {
|
|
return err
|
|
}
|
|
return s.transition("running")
|
|
}
|
|
|
|
func (s *createdState) Delete(ctx context.Context) error {
|
|
if err := s.p.delete(ctx); err != nil {
|
|
return err
|
|
}
|
|
return s.transition("deleted")
|
|
}
|
|
|
|
func (s *createdState) Kill(ctx context.Context, sig uint32, all bool) error {
|
|
return s.p.kill(ctx, sig, all)
|
|
}
|
|
|
|
func (s *createdState) SetExited(status int) {
|
|
s.p.setExited(status)
|
|
|
|
if err := s.transition("stopped"); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (proc.Process, error) {
|
|
return s.p.exec(ctx, path, r)
|
|
}
|
|
|
|
type createdCheckpointState struct {
|
|
p *Init
|
|
opts *runc.RestoreOpts
|
|
}
|
|
|
|
func (s *createdCheckpointState) transition(name string) error {
|
|
switch name {
|
|
case "running":
|
|
s.p.initState = &runningState{p: s.p}
|
|
case "stopped":
|
|
s.p.initState = &stoppedState{p: s.p}
|
|
case "deleted":
|
|
s.p.initState = &deletedState{}
|
|
default:
|
|
return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *createdCheckpointState) Pause(ctx context.Context) error {
|
|
return errors.Errorf("cannot pause task in created state")
|
|
}
|
|
|
|
func (s *createdCheckpointState) Resume(ctx context.Context) error {
|
|
return errors.Errorf("cannot resume task in created state")
|
|
}
|
|
|
|
func (s *createdCheckpointState) Update(ctx context.Context, r *google_protobuf.Any) error {
|
|
return s.p.update(ctx, r)
|
|
}
|
|
|
|
func (s *createdCheckpointState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
|
|
return errors.Errorf("cannot checkpoint a task in created state")
|
|
}
|
|
|
|
func (s *createdCheckpointState) Resize(ws console.WinSize) error {
|
|
return s.p.resize(ws)
|
|
}
|
|
|
|
func (s *createdCheckpointState) Start(ctx context.Context) error {
|
|
p := s.p
|
|
sio := p.stdio
|
|
|
|
var (
|
|
err error
|
|
socket *runc.Socket
|
|
)
|
|
if sio.Terminal {
|
|
if socket, err = runc.NewTempConsoleSocket(); err != nil {
|
|
return errors.Wrap(err, "failed to create OCI runtime console socket")
|
|
}
|
|
defer socket.Close()
|
|
s.opts.ConsoleSocket = socket
|
|
}
|
|
|
|
if _, err := s.p.runtime.Restore(ctx, p.id, p.Bundle, s.opts); err != nil {
|
|
return p.runtimeError(err, "OCI runtime restore failed")
|
|
}
|
|
if sio.Stdin != "" {
|
|
if err := p.openStdin(sio.Stdin); err != nil {
|
|
return errors.Wrapf(err, "failed to open stdin fifo %s", sio.Stdin)
|
|
}
|
|
}
|
|
if socket != nil {
|
|
console, err := socket.ReceiveMaster()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to retrieve console master")
|
|
}
|
|
console, err = p.Platform.CopyConsole(ctx, console, sio.Stdin, sio.Stdout, sio.Stderr, &p.wg)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to start console copy")
|
|
}
|
|
p.console = console
|
|
} else {
|
|
if err := p.io.Copy(ctx, &p.wg); err != nil {
|
|
return errors.Wrap(err, "failed to start io pipe copy")
|
|
}
|
|
}
|
|
pid, err := runc.ReadPidFile(s.opts.PidFile)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to retrieve OCI runtime container pid")
|
|
}
|
|
p.pid = pid
|
|
return s.transition("running")
|
|
}
|
|
|
|
func (s *createdCheckpointState) Delete(ctx context.Context) error {
|
|
if err := s.p.delete(ctx); err != nil {
|
|
return err
|
|
}
|
|
return s.transition("deleted")
|
|
}
|
|
|
|
func (s *createdCheckpointState) Kill(ctx context.Context, sig uint32, all bool) error {
|
|
return s.p.kill(ctx, sig, all)
|
|
}
|
|
|
|
func (s *createdCheckpointState) SetExited(status int) {
|
|
s.p.setExited(status)
|
|
|
|
if err := s.transition("stopped"); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *createdCheckpointState) Exec(ctx context.Context, path string, r *ExecConfig) (proc.Process, error) {
|
|
return nil, errors.Errorf("cannot exec in a created state")
|
|
}
|
|
|
|
type runningState struct {
|
|
p *Init
|
|
}
|
|
|
|
func (s *runningState) transition(name string) error {
|
|
switch name {
|
|
case "stopped":
|
|
s.p.initState = &stoppedState{p: s.p}
|
|
case "paused":
|
|
s.p.initState = &pausedState{p: s.p}
|
|
default:
|
|
return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *runningState) Pause(ctx context.Context) error {
|
|
if err := s.p.runtime.Pause(ctx, s.p.id); err != nil {
|
|
return s.p.runtimeError(err, "OCI runtime pause failed")
|
|
}
|
|
|
|
return s.transition("paused")
|
|
}
|
|
|
|
func (s *runningState) Resume(ctx context.Context) error {
|
|
return errors.Errorf("cannot resume a running process")
|
|
}
|
|
|
|
func (s *runningState) Update(ctx context.Context, r *google_protobuf.Any) error {
|
|
return s.p.update(ctx, r)
|
|
}
|
|
|
|
func (s *runningState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
|
|
return s.p.checkpoint(ctx, r)
|
|
}
|
|
|
|
func (s *runningState) Resize(ws console.WinSize) error {
|
|
return s.p.resize(ws)
|
|
}
|
|
|
|
func (s *runningState) Start(ctx context.Context) error {
|
|
return errors.Errorf("cannot start a running process")
|
|
}
|
|
|
|
func (s *runningState) Delete(ctx context.Context) error {
|
|
return errors.Errorf("cannot delete a running process")
|
|
}
|
|
|
|
func (s *runningState) Kill(ctx context.Context, sig uint32, all bool) error {
|
|
return s.p.kill(ctx, sig, all)
|
|
}
|
|
|
|
func (s *runningState) SetExited(status int) {
|
|
s.p.setExited(status)
|
|
|
|
if err := s.transition("stopped"); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *runningState) Exec(ctx context.Context, path string, r *ExecConfig) (proc.Process, error) {
|
|
return s.p.exec(ctx, path, r)
|
|
}
|
|
|
|
type pausedState struct {
|
|
p *Init
|
|
}
|
|
|
|
func (s *pausedState) transition(name string) error {
|
|
switch name {
|
|
case "running":
|
|
s.p.initState = &runningState{p: s.p}
|
|
case "stopped":
|
|
s.p.initState = &stoppedState{p: s.p}
|
|
default:
|
|
return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *pausedState) Pause(ctx context.Context) error {
|
|
return errors.Errorf("cannot pause a paused container")
|
|
}
|
|
|
|
func (s *pausedState) Resume(ctx context.Context) error {
|
|
if err := s.p.runtime.Resume(ctx, s.p.id); err != nil {
|
|
return s.p.runtimeError(err, "OCI runtime resume failed")
|
|
}
|
|
|
|
return s.transition("running")
|
|
}
|
|
|
|
func (s *pausedState) Update(ctx context.Context, r *google_protobuf.Any) error {
|
|
return s.p.update(ctx, r)
|
|
}
|
|
|
|
func (s *pausedState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
|
|
return s.p.checkpoint(ctx, r)
|
|
}
|
|
|
|
func (s *pausedState) Resize(ws console.WinSize) error {
|
|
return s.p.resize(ws)
|
|
}
|
|
|
|
func (s *pausedState) Start(ctx context.Context) error {
|
|
return errors.Errorf("cannot start a paused process")
|
|
}
|
|
|
|
func (s *pausedState) Delete(ctx context.Context) error {
|
|
return errors.Errorf("cannot delete a paused process")
|
|
}
|
|
|
|
func (s *pausedState) Kill(ctx context.Context, sig uint32, all bool) error {
|
|
return s.p.kill(ctx, sig, all)
|
|
}
|
|
|
|
func (s *pausedState) SetExited(status int) {
|
|
s.p.setExited(status)
|
|
|
|
if err := s.p.runtime.Resume(context.Background(), s.p.id); err != nil {
|
|
logrus.WithError(err).Error("resuming exited container from paused state")
|
|
}
|
|
|
|
if err := s.transition("stopped"); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *pausedState) Exec(ctx context.Context, path string, r *ExecConfig) (proc.Process, error) {
|
|
return nil, errors.Errorf("cannot exec in a paused state")
|
|
}
|
|
|
|
type stoppedState struct {
|
|
p *Init
|
|
}
|
|
|
|
func (s *stoppedState) transition(name string) error {
|
|
switch name {
|
|
case "deleted":
|
|
s.p.initState = &deletedState{}
|
|
default:
|
|
return errors.Errorf("invalid state transition %q to %q", stateName(s), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *stoppedState) Pause(ctx context.Context) error {
|
|
return errors.Errorf("cannot pause a stopped container")
|
|
}
|
|
|
|
func (s *stoppedState) Resume(ctx context.Context) error {
|
|
return errors.Errorf("cannot resume a stopped container")
|
|
}
|
|
|
|
func (s *stoppedState) Update(ctx context.Context, r *google_protobuf.Any) error {
|
|
return errors.Errorf("cannot update a stopped container")
|
|
}
|
|
|
|
func (s *stoppedState) Checkpoint(ctx context.Context, r *CheckpointConfig) error {
|
|
return errors.Errorf("cannot checkpoint a stopped container")
|
|
}
|
|
|
|
func (s *stoppedState) Resize(ws console.WinSize) error {
|
|
return errors.Errorf("cannot resize a stopped container")
|
|
}
|
|
|
|
func (s *stoppedState) Start(ctx context.Context) error {
|
|
return errors.Errorf("cannot start a stopped process")
|
|
}
|
|
|
|
func (s *stoppedState) Delete(ctx context.Context) error {
|
|
if err := s.p.delete(ctx); err != nil {
|
|
return err
|
|
}
|
|
return s.transition("deleted")
|
|
}
|
|
|
|
func (s *stoppedState) Kill(ctx context.Context, sig uint32, all bool) error {
|
|
return s.p.kill(ctx, sig, all)
|
|
}
|
|
|
|
func (s *stoppedState) SetExited(status int) {
|
|
// no op
|
|
}
|
|
|
|
func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (proc.Process, error) {
|
|
return nil, errors.Errorf("cannot exec in a stopped state")
|
|
}
|