You've already forked runc
mirror of
https://github.com/opencontainers/runc.git
synced 2025-11-09 13:00:56 +03:00
sd-notify: do not hang when NOTIFY_SOCKET is used with create
if NOTIFY_SOCKET is used, do not block the main runc process waiting for events on the notify socket. Bind mount the parent directory of the notify socket, so that "start" can create the socket and it is still accessible from the container. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
112
notify_socket.go
112
notify_socket.go
@@ -7,11 +7,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,12 +29,12 @@ func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
root := filepath.Join(context.GlobalString("root"), id)
|
root := filepath.Join(context.GlobalString("root"), id)
|
||||||
path := filepath.Join(root, "notify.sock")
|
socketPath := filepath.Join(root, "notify", "notify.sock")
|
||||||
|
|
||||||
notifySocket := ¬ifySocket{
|
notifySocket := ¬ifySocket{
|
||||||
socket: nil,
|
socket: nil,
|
||||||
host: notifySocketHost,
|
host: notifySocketHost,
|
||||||
socketPath: path,
|
socketPath: socketPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifySocket
|
return notifySocket
|
||||||
@@ -44,13 +46,19 @@ func (s *notifySocket) Close() error {
|
|||||||
|
|
||||||
// If systemd is supporting sd_notify protocol, this function will add support
|
// If systemd is supporting sd_notify protocol, this function will add support
|
||||||
// for sd_notify protocol from within the container.
|
// for sd_notify protocol from within the container.
|
||||||
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
|
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) error {
|
||||||
mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
|
pathInContainer := filepath.Join("/run/notify", path.Base(s.socketPath))
|
||||||
|
mount := specs.Mount{
|
||||||
|
Destination: path.Dir(pathInContainer),
|
||||||
|
Source: path.Dir(s.socketPath),
|
||||||
|
Options: []string{"bind", "nosuid", "noexec", "nodev", "ro"},
|
||||||
|
}
|
||||||
spec.Mounts = append(spec.Mounts, mount)
|
spec.Mounts = append(spec.Mounts, mount)
|
||||||
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
|
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", pathInContainer))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *notifySocket) setupSocket() error {
|
func (s *notifySocket) bindSocket() error {
|
||||||
addr := net.UnixAddr{
|
addr := net.UnixAddr{
|
||||||
Name: s.socketPath,
|
Name: s.socketPath,
|
||||||
Net: "unixgram",
|
Net: "unixgram",
|
||||||
@@ -71,46 +79,92 @@ func (s *notifySocket) setupSocket() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pid1 must be set only with -d, as it is used to set the new process as the main process
|
func (s *notifySocket) setupSocketDirectory() error {
|
||||||
// for the service in systemd
|
return os.Mkdir(path.Dir(s.socketPath), 0755)
|
||||||
func (s *notifySocket) run(pid1 int) {
|
}
|
||||||
buf := make([]byte, 512)
|
|
||||||
notifySocketHostAddr := net.UnixAddr{Name: s.host, Net: "unixgram"}
|
func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*notifySocket, error) {
|
||||||
|
notifySocket := newNotifySocket(context, notifySocketHost, id)
|
||||||
|
if notifySocket == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := notifySocket.bindSocket(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return notifySocket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notifySocket) waitForContainer(container libcontainer.Container) error {
|
||||||
|
s, err := container.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return n.run(s.InitProcessPid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notifySocket) run(pid1 int) error {
|
||||||
|
if n.socket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
notifySocketHostAddr := net.UnixAddr{Name: n.host, Net: "unixgram"}
|
||||||
client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr)
|
client, err := net.DialUnix("unixgram", nil, ¬ifySocketHostAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Millisecond * 100)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
fileChan := make(chan []byte)
|
||||||
|
go func() {
|
||||||
for {
|
for {
|
||||||
r, err := s.socket.Read(buf)
|
buf := make([]byte, 4096)
|
||||||
if err != nil {
|
r, err := n.socket.Read(buf)
|
||||||
break
|
|
||||||
}
|
|
||||||
var out bytes.Buffer
|
|
||||||
for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
|
|
||||||
if bytes.HasPrefix(line, []byte("READY=")) {
|
|
||||||
_, err = out.Write(line)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
got := buf[0:r]
|
||||||
|
// systemd-ready sends a single datagram with the state string as payload,
|
||||||
|
// so we don't need to worry about partial messages.
|
||||||
|
for _, line := range bytes.Split(got, []byte{'\n'}) {
|
||||||
|
if bytes.HasPrefix(got, []byte("READY=")) {
|
||||||
|
fileChan <- line
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid1)))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case b := <-fileChan:
|
||||||
|
var out bytes.Buffer
|
||||||
|
_, err = out.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = out.Write([]byte{'\n'})
|
_, err = out.Write([]byte{'\n'})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.Write(out.Bytes())
|
_, err = client.Write(out.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we can inform systemd to use pid1 as the pid to monitor
|
// now we can inform systemd to use pid1 as the pid to monitor
|
||||||
if pid1 > 0 {
|
|
||||||
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
|
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
|
||||||
client.Write([]byte(newPid))
|
client.Write([]byte(newPid))
|
||||||
}
|
return nil
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
|
|||||||
h.notifySocket.run(pid1)
|
h.notifySocket.run(pid1)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
h.notifySocket.run(os.Getpid())
|
||||||
go h.notifySocket.run(0)
|
go h.notifySocket.run(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +98,6 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
|
|||||||
// status because we must ensure that any of the go specific process
|
// status because we must ensure that any of the go specific process
|
||||||
// fun such as flushing pipes are complete before we return.
|
// fun such as flushing pipes are complete before we return.
|
||||||
process.Wait()
|
process.Wait()
|
||||||
if h.notifySocket != nil {
|
|
||||||
h.notifySocket.Close()
|
|
||||||
}
|
|
||||||
return e.status, nil
|
return e.status, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
start.go
13
start.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@@ -31,7 +32,17 @@ your host.`,
|
|||||||
}
|
}
|
||||||
switch status {
|
switch status {
|
||||||
case libcontainer.Created:
|
case libcontainer.Created:
|
||||||
return container.Exec()
|
notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := container.Exec(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if notifySocket != nil {
|
||||||
|
return notifySocket.waitForContainer(container)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case libcontainer.Stopped:
|
case libcontainer.Stopped:
|
||||||
return errors.New("cannot start a container that has stopped")
|
return errors.New("cannot start a container that has stopped")
|
||||||
case libcontainer.Running:
|
case libcontainer.Running:
|
||||||
|
|||||||
@@ -408,7 +408,9 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
|
|||||||
|
|
||||||
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
|
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
|
||||||
if notifySocket != nil {
|
if notifySocket != nil {
|
||||||
notifySocket.setupSpec(context, spec)
|
if err := notifySocket.setupSpec(context, spec); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := createContainer(context, id, spec)
|
container, err := createContainer(context, id, spec)
|
||||||
@@ -417,10 +419,16 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if notifySocket != nil {
|
if notifySocket != nil {
|
||||||
err := notifySocket.setupSocket()
|
err := notifySocket.setupSocketDirectory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
if action == CT_ACT_RUN {
|
||||||
|
err := notifySocket.bindSocket()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support on-demand socket activation by passing file descriptors into the container init process.
|
// Support on-demand socket activation by passing file descriptors into the container init process.
|
||||||
|
|||||||
Reference in New Issue
Block a user