Add journaling
This commit is contained in:
parent
d34d482a5f
commit
6ff2239019
5 changed files with 115 additions and 86 deletions
|
@ -49,12 +49,11 @@ func (s *server) signalPid(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e := &containerd.SignalEvent{
|
|
||||||
ID: id,
|
e := containerd.NewEvent(containerd.SignalEventType)
|
||||||
Pid: pid,
|
e.ID = id
|
||||||
Signal: syscall.Signal(signal.Signal),
|
e.Pid = pid
|
||||||
Err: make(chan error, 1),
|
e.Signal = syscall.Signal(signal.Signal)
|
||||||
}
|
|
||||||
s.supervisor.SendEvent(e)
|
s.supervisor.SendEvent(e)
|
||||||
if err := <-e.Err; err != nil {
|
if err := <-e.Err; err != nil {
|
||||||
status := http.StatusInternalServerError
|
status := http.StatusInternalServerError
|
||||||
|
@ -69,9 +68,7 @@ func (s *server) signalPid(w http.ResponseWriter, r *http.Request) {
|
||||||
func (s *server) containers(w http.ResponseWriter, r *http.Request) {
|
func (s *server) containers(w http.ResponseWriter, r *http.Request) {
|
||||||
var state State
|
var state State
|
||||||
state.Containers = []Container{}
|
state.Containers = []Container{}
|
||||||
e := &containerd.GetContainersEvent{
|
e := containerd.NewEvent(containerd.GetContainerEventType)
|
||||||
Err: make(chan error, 1),
|
|
||||||
}
|
|
||||||
s.supervisor.SendEvent(e)
|
s.supervisor.SendEvent(e)
|
||||||
if err := <-e.Err; err != nil {
|
if err := <-e.Err; err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -112,11 +109,9 @@ func (s *server) createContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "empty bundle path", http.StatusBadRequest)
|
http.Error(w, "empty bundle path", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e := &containerd.StartContainerEvent{
|
e := containerd.NewEvent(containerd.StartContainerEventType)
|
||||||
ID: id,
|
e.ID = id
|
||||||
BundlePath: c.BundlePath,
|
e.BundlePath = c.BundlePath
|
||||||
Err: make(chan error, 1),
|
|
||||||
}
|
|
||||||
s.supervisor.SendEvent(e)
|
s.supervisor.SendEvent(e)
|
||||||
if err := <-e.Err; err != nil {
|
if err := <-e.Err; err != nil {
|
||||||
code := http.StatusInternalServerError
|
code := http.StatusInternalServerError
|
||||||
|
|
|
@ -54,7 +54,7 @@ func daemon(stateDir string, concurrency, bufferSize int) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
events := make(chan containerd.Event, bufferSize)
|
events := make(chan *containerd.Event, bufferSize)
|
||||||
// start the signal handler in the background.
|
// start the signal handler in the background.
|
||||||
go startSignalHandler(supervisor, bufferSize)
|
go startSignalHandler(supervisor, bufferSize)
|
||||||
if err := supervisor.Start(events); err != nil {
|
if err := supervisor.Start(events); err != nil {
|
||||||
|
@ -71,6 +71,7 @@ func startSignalHandler(supervisor *containerd.Supervisor, bufferSize int) {
|
||||||
for s := range signals {
|
for s := range signals {
|
||||||
switch s {
|
switch s {
|
||||||
case syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP:
|
case syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP:
|
||||||
|
supervisor.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
case syscall.SIGCHLD:
|
case syscall.SIGCHLD:
|
||||||
exits, err := reap()
|
exits, err := reap()
|
||||||
|
@ -84,7 +85,7 @@ func startSignalHandler(supervisor *containerd.Supervisor, bufferSize int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reap() (exits []*containerd.ExitEvent, err error) {
|
func reap() (exits []*containerd.Event, err error) {
|
||||||
var (
|
var (
|
||||||
ws syscall.WaitStatus
|
ws syscall.WaitStatus
|
||||||
rus syscall.Rusage
|
rus syscall.Rusage
|
||||||
|
@ -100,9 +101,9 @@ func reap() (exits []*containerd.ExitEvent, err error) {
|
||||||
if pid <= 0 {
|
if pid <= 0 {
|
||||||
return exits, nil
|
return exits, nil
|
||||||
}
|
}
|
||||||
exits = append(exits, &containerd.ExitEvent{
|
e := containerd.NewEvent(containerd.ExitEventType)
|
||||||
Pid: pid,
|
e.Pid = pid
|
||||||
Status: utils.ExitStatus(ws),
|
e.Status = utils.ExitStatus(ws)
|
||||||
})
|
exits = append(exits, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
event.go
83
event.go
|
@ -1,59 +1,36 @@
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Event interface {
|
type EventType string
|
||||||
String() string
|
|
||||||
|
const (
|
||||||
|
ExitEventType EventType = "exit"
|
||||||
|
StartContainerEventType EventType = "startContainer"
|
||||||
|
ContainerStartErrorEventType EventType = "startContainerError"
|
||||||
|
GetContainerEventType EventType = "getContainer"
|
||||||
|
SignalEventType EventType = "signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEvent(t EventType) *Event {
|
||||||
|
return &Event{
|
||||||
|
Type: t,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Err: make(chan error, 1),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackEvent interface {
|
type Event struct {
|
||||||
Event() Event
|
Type EventType `json:"type"`
|
||||||
Callback() chan Event
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
ID string `json:"id,omitempty"`
|
||||||
|
BundlePath string `json:"bundlePath,omitempty"`
|
||||||
type ExitEvent struct {
|
Pid int `json:"pid,omitempty"`
|
||||||
Pid int
|
Status int `json:"status,omitempty"`
|
||||||
Status int
|
Signal os.Signal `json:"signal,omitempty"`
|
||||||
}
|
Containers []Container `json:"-"`
|
||||||
|
Err chan error `json:"-"`
|
||||||
func (e *ExitEvent) String() string {
|
|
||||||
return "exit event"
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartContainerEvent struct {
|
|
||||||
ID string
|
|
||||||
BundlePath string
|
|
||||||
Err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *StartContainerEvent) String() string {
|
|
||||||
return "create container"
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContainerStartErrorEvent struct {
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerStartErrorEvent) String() string {
|
|
||||||
return "container start error"
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetContainersEvent struct {
|
|
||||||
Containers []Container
|
|
||||||
Err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *GetContainersEvent) String() string {
|
|
||||||
return "get containers"
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignalEvent struct {
|
|
||||||
ID string
|
|
||||||
Pid int
|
|
||||||
Signal os.Signal
|
|
||||||
Err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalEvent) String() string {
|
|
||||||
return "signal event"
|
|
||||||
}
|
}
|
||||||
|
|
41
journal.go
Normal file
41
journal.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package containerd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type entry struct {
|
||||||
|
Event *Event `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJournal(path string) (*journal, error) {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &journal{
|
||||||
|
f: f,
|
||||||
|
enc: json.NewEncoder(f),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type journal struct {
|
||||||
|
f *os.File
|
||||||
|
enc *json.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *journal) write(e *Event) error {
|
||||||
|
et := &entry{
|
||||||
|
Event: e,
|
||||||
|
}
|
||||||
|
return j.enc.Encode(et)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *journal) Close() error {
|
||||||
|
return j.f.Close()
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -18,11 +19,16 @@ func NewSupervisor(stateDir string, concurrency int) (*Supervisor, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
j, err := newJournal(filepath.Join(stateDir, "journal.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
s := &Supervisor{
|
s := &Supervisor{
|
||||||
stateDir: stateDir,
|
stateDir: stateDir,
|
||||||
containers: make(map[string]Container),
|
containers: make(map[string]Container),
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
tasks: make(chan *startTask, concurrency*100),
|
tasks: make(chan *startTask, concurrency*100),
|
||||||
|
journal: j,
|
||||||
}
|
}
|
||||||
for i := 0; i < concurrency; i++ {
|
for i := 0; i < concurrency; i++ {
|
||||||
s.workerGroup.Add(1)
|
s.workerGroup.Add(1)
|
||||||
|
@ -39,24 +45,33 @@ type Supervisor struct {
|
||||||
|
|
||||||
runtime Runtime
|
runtime Runtime
|
||||||
|
|
||||||
events chan Event
|
journal *journal
|
||||||
|
|
||||||
|
events chan *Event
|
||||||
tasks chan *startTask
|
tasks chan *startTask
|
||||||
workerGroup sync.WaitGroup
|
workerGroup sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Supervisor) Close() error {
|
||||||
|
return s.journal.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Start is a non-blocking call that runs the supervisor for monitoring contianer processes and
|
// Start is a non-blocking call that runs the supervisor for monitoring contianer processes and
|
||||||
// executing new containers.
|
// executing new containers.
|
||||||
//
|
//
|
||||||
// This event loop is the only thing that is allowed to modify state of containers and processes.
|
// This event loop is the only thing that is allowed to modify state of containers and processes.
|
||||||
func (s *Supervisor) Start(events chan Event) error {
|
func (s *Supervisor) Start(events chan *Event) error {
|
||||||
if events == nil {
|
if events == nil {
|
||||||
return ErrEventChanNil
|
return ErrEventChanNil
|
||||||
}
|
}
|
||||||
s.events = events
|
s.events = events
|
||||||
go func() {
|
go func() {
|
||||||
for evt := range events {
|
for e := range events {
|
||||||
switch e := evt.(type) {
|
if err := s.journal.write(e); err != nil {
|
||||||
case *ExitEvent:
|
logrus.WithField("error", err).Error("write journal entry")
|
||||||
|
}
|
||||||
|
switch e.Type {
|
||||||
|
case ExitEventType:
|
||||||
logrus.WithFields(logrus.Fields{"pid": e.Pid, "status": e.Status}).
|
logrus.WithFields(logrus.Fields{"pid": e.Pid, "status": e.Status}).
|
||||||
Debug("containerd: process exited")
|
Debug("containerd: process exited")
|
||||||
container, err := s.getContainerForPid(e.Pid)
|
container, err := s.getContainerForPid(e.Pid)
|
||||||
|
@ -70,7 +85,7 @@ func (s *Supervisor) Start(events chan Event) error {
|
||||||
if err := s.deleteContainer(container); err != nil {
|
if err := s.deleteContainer(container); err != nil {
|
||||||
logrus.WithField("error", err).Error("containerd: deleting container")
|
logrus.WithField("error", err).Error("containerd: deleting container")
|
||||||
}
|
}
|
||||||
case *StartContainerEvent:
|
case StartContainerEventType:
|
||||||
container, err := s.runtime.Create(e.ID, e.BundlePath)
|
container, err := s.runtime.Create(e.ID, e.BundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.Err <- err
|
e.Err <- err
|
||||||
|
@ -81,18 +96,18 @@ func (s *Supervisor) Start(events chan Event) error {
|
||||||
err: e.Err,
|
err: e.Err,
|
||||||
container: container,
|
container: container,
|
||||||
}
|
}
|
||||||
case *ContainerStartErrorEvent:
|
case ContainerStartErrorEventType:
|
||||||
if container, ok := s.containers[e.ID]; ok {
|
if container, ok := s.containers[e.ID]; ok {
|
||||||
if err := s.deleteContainer(container); err != nil {
|
if err := s.deleteContainer(container); err != nil {
|
||||||
logrus.WithField("error", err).Error("containerd: deleting container")
|
logrus.WithField("error", err).Error("containerd: deleting container")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *GetContainersEvent:
|
case GetContainerEventType:
|
||||||
for _, c := range s.containers {
|
for _, c := range s.containers {
|
||||||
e.Containers = append(e.Containers, c)
|
e.Containers = append(e.Containers, c)
|
||||||
}
|
}
|
||||||
e.Err <- nil
|
e.Err <- nil
|
||||||
case *SignalEvent:
|
case SignalEventType:
|
||||||
container, ok := s.containers[e.ID]
|
container, ok := s.containers[e.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
e.Err <- ErrContainerNotFound
|
e.Err <- ErrContainerNotFound
|
||||||
|
@ -139,7 +154,7 @@ func (s *Supervisor) getContainerForPid(pid int) (Container, error) {
|
||||||
return nil, errNoContainerForPid
|
return nil, errNoContainerForPid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Supervisor) SendEvent(evt Event) {
|
func (s *Supervisor) SendEvent(evt *Event) {
|
||||||
s.events <- evt
|
s.events <- evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,9 +167,9 @@ func (s *Supervisor) startContainerWorker(tasks chan *startTask) {
|
||||||
defer s.workerGroup.Done()
|
defer s.workerGroup.Done()
|
||||||
for t := range tasks {
|
for t := range tasks {
|
||||||
if err := t.container.Start(); err != nil {
|
if err := t.container.Start(); err != nil {
|
||||||
s.SendEvent(&ContainerStartErrorEvent{
|
e := NewEvent(StartContainerEventType)
|
||||||
ID: t.container.ID(),
|
e.ID = t.container.ID()
|
||||||
})
|
s.SendEvent(e)
|
||||||
t.err <- err
|
t.err <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue