mirror of https://github.com/knative/func.git
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:
parent
cba8ee52c4
commit
966a150c58
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue