fix: podman auto-svc has own control group (#1239)

This keeps the podman process alive while `func` receives
SIGKILL or SIGTERM.
We must keep podman alive for cleanup (e.g. container removal).

Signed-off-by: Matej Vasek <mvasek@redhat.com>

Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek 2022-09-14 09:21:49 +02:00 committed by GitHub
parent cba8ee52c4
commit 966a150c58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 79 deletions

View File

@ -1,20 +1,14 @@
package docker package docker
import ( import (
"bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"syscall"
"time"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -172,79 +166,6 @@ func podmanPresent() bool {
return err == nil return err == nil
} }
// creates a docker client that has its own podman service associated with it
// the service is shutdown when Close() is called on the client
func newClientWithPodmanService() (dockerClient client.CommonAPIClient, dockerHost string, err error) {
tmpDir, err := os.MkdirTemp("", "func-podman-")
if err != nil {
return
}
podmanSocket := filepath.Join(tmpDir, "podman.sock")
dockerHost = fmt.Sprintf("unix://%s", podmanSocket)
cmd := exec.Command("podman", "system", "service", dockerHost, "--time=0")
outBuff := bytes.Buffer{}
cmd.Stdout = &outBuff
cmd.Stderr = &outBuff
err = cmd.Start()
if err != nil {
return
}
waitErrCh := make(chan error)
go func() { waitErrCh <- cmd.Wait() }()
dockerClient, err = client.NewClientWithOpts(client.FromEnv, client.WithHost(dockerHost), client.WithAPIVersionNegotiation())
stopPodmanService := func() {
_ = cmd.Process.Signal(syscall.SIGTERM)
_ = os.RemoveAll(tmpDir)
select {
case <-waitErrCh:
// the podman service has been shutdown, we don't care about error
return
case <-time.After(time.Second * 1):
// failed to gracefully shutdown the podman service, sending SIGKILL
_ = cmd.Process.Signal(syscall.SIGKILL)
}
}
dockerClient = clientWithAdditionalCleanup{
CommonAPIClient: dockerClient,
cleanUp: stopPodmanService,
}
svcUpCh := make(chan struct{})
go func() {
// give a time to podman to start
for i := 0; i < 40; i++ {
if _, e := dockerClient.Ping(context.Background()); e == nil {
svcUpCh <- struct{}{}
}
time.Sleep(time.Millisecond * 250)
}
}()
select {
case <-svcUpCh:
return
case <-time.After(time.Second * 10):
stopPodmanService()
err = errors.New("the podman service has not come up in time")
case err = <-waitErrCh:
// If this `case` is not selected then the waitErrCh is eventually read by calling stopPodmanService
if err != nil {
err = fmt.Errorf("failed to start the podman service (cmd out: %q): %w", outBuff.String(), err)
} else {
err = fmt.Errorf("the podman process exited before the service come up (cmd out: %q)", outBuff.String())
}
}
return
}
type clientWithAdditionalCleanup struct { type clientWithAdditionalCleanup struct {
client.CommonAPIClient client.CommonAPIClient
cleanUp func() cleanUp func()

View File

@ -0,0 +1,89 @@
package docker
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
"github.com/docker/docker/client"
)
// creates a docker client that has its own podman service associated with it
// the service is shutdown when Close() is called on the client
func newClientWithPodmanService() (dockerClient client.CommonAPIClient, dockerHost string, err error) {
tmpDir, err := os.MkdirTemp("", "func-podman-")
if err != nil {
return
}
podmanSocket := filepath.Join(tmpDir, "podman.sock")
dockerHost = fmt.Sprintf("unix://%s", podmanSocket)
cmd := exec.Command("podman", "system", "service", dockerHost, "--time=0")
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pgid: 0}
outBuff := bytes.Buffer{}
cmd.Stdout = &outBuff
cmd.Stderr = &outBuff
err = cmd.Start()
if err != nil {
return
}
waitErrCh := make(chan error)
go func() { waitErrCh <- cmd.Wait() }()
dockerClient, err = client.NewClientWithOpts(client.FromEnv, client.WithHost(dockerHost), client.WithAPIVersionNegotiation())
stopPodmanService := func() {
_ = cmd.Process.Signal(syscall.SIGTERM)
_ = os.RemoveAll(tmpDir)
select {
case <-waitErrCh:
// the podman service has been shutdown, we don't care about error
return
case <-time.After(time.Second * 1):
// failed to gracefully shutdown the podman service, sending SIGKILL
_ = cmd.Process.Signal(syscall.SIGKILL)
}
}
dockerClient = clientWithAdditionalCleanup{
CommonAPIClient: dockerClient,
cleanUp: stopPodmanService,
}
svcUpCh := make(chan struct{})
go func() {
// give a time to podman to start
for i := 0; i < 40; i++ {
if _, e := dockerClient.Ping(context.Background()); e == nil {
svcUpCh <- struct{}{}
}
time.Sleep(time.Millisecond * 250)
}
}()
select {
case <-svcUpCh:
return
case <-time.After(time.Second * 10):
stopPodmanService()
err = errors.New("the podman service has not come up in time")
case err = <-waitErrCh:
// If this `case` is not selected then the waitErrCh is eventually read by calling stopPodmanService
if err != nil {
err = fmt.Errorf("failed to start the podman service (cmd out: %q): %w", outBuff.String(), err)
} else {
err = fmt.Errorf("the podman process exited before the service come up (cmd out: %q)", outBuff.String())
}
}
return
}

View File

@ -0,0 +1,10 @@
//go:build !linux
// +build !linux
package docker
import "github.com/docker/docker/client"
func newClientWithPodmanService() (dockerClient client.CommonAPIClient, dockerHost string, err error) {
panic("only implemented on Linux")
}