Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Envoy entrypoint in Go #42

Merged
merged 15 commits into from
Nov 2, 2021
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ version: 2.1
executors:
go:
docker:
- image: docker.mirror.hashicorp.services/circleci/golang:1.14
- image: docker.mirror.hashicorp.services/circleci/golang:1.17
environment:
TEST_RESULTS: /tmp/test-results # path to where test results are saved
CONSUL_VERSION: 1.9.4 # Consul's OSS version to use in tests
CONSUL_VERSION: 1.10.3 # Consul's OSS version to use in tests

jobs:
go-fmt-and-vet:
Expand Down Expand Up @@ -127,7 +127,7 @@ workflows:
- go-fmt-and-vet
- linters
- build-distro:
OS: "darwin freebsd linux windows"
OS: "freebsd linux windows"
ARCH: "386"
name: build-distros-386
requires:
Expand Down
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## UNRELEASED

BREAKING CHANGES
* `consul-ecs` docker images no longer have the `consul` binary. The
mesh-init subcommand still expects the `consul` binary on the
`$PATH`. [[GH-40](https://github.com/hashicorp/consul-ecs/pull/40)]
* mesh-init: The `-envoy-bootstrap-file` option is removed, and replaced with `-envoy-bootstrap-dir`.
The Envoy bootstrap config file is written to `envoy-bootstrap.json` within that directory.
[[GH-42](https://github.com/hashicorp/consul-ecs/pull/42)]

FEATURES
* Add a `health-sync` subcommand to sync ECS health checks into Consul [[GH-33](https://github.com/hashicorp/consul-ecs/pull/33)]
* Add the `-health-sync-containers` flag to `mesh-init` [[GH-36](https://github.com/hashicorp/consul-ecs/pull/36)]
Expand All @@ -9,11 +17,8 @@ FEATURES
`consul.hashicorp.com/service-name` tag on the ECS task. If the tag
does not exist, it uses the Task family as the Consul service name.
[[GH-44](https://github.com/hashicorp/consul-ecs/pull/44)]

BREAKING CHANGES
* `consul-ecs` docker images no longer have the `consul` binary. The
mesh-init subcommand still expects the `consul` binary on the
`$PATH`. [[GH-40](https://github.com/hashicorp/consul-ecs/pull/40)]
* Add a `envoy-entrypoint` subcommand, which can be used as the entrypoint to the Envoy container running in ECS
to support graceful shutdown. [[GH-42](https://github.com/hashicorp/consul-ecs/pull/42)]

## 0.2.0-beta2 (September 30, 2021)
IMPROVEMENTS
Expand Down
6 changes: 4 additions & 2 deletions awsutil/awsutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ type ECSTaskMeta struct {
}

type ECSTaskMetaContainer struct {
Name string `json:"Name"`
Health ECSTaskMetaHealth `json:"Health"`
Name string `json:"Name"`
Health ECSTaskMetaHealth `json:"Health"`
DesiredStatus string `json:"DesiredStatus"`
KnownStatus string `json:"KnownStatus"`
}

type ECSTaskMetaHealth struct {
Expand Down
4 changes: 4 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

cmdController "github.com/hashicorp/consul-ecs/subcommand/acl-controller"
cmdEnvoyEntrypoint "github.com/hashicorp/consul-ecs/subcommand/envoy-entrypoint"
cmdHealthSync "github.com/hashicorp/consul-ecs/subcommand/health-sync"
cmdMeshInit "github.com/hashicorp/consul-ecs/subcommand/mesh-init"
cmdVersion "github.com/hashicorp/consul-ecs/subcommand/version"
Expand All @@ -30,6 +31,9 @@ func init() {
"health-sync": func() (cli.Command, error) {
return &cmdHealthSync.Command{UI: ui}, nil
},
"envoy-entrypoint": func() (cli.Command, error) {
return &cmdEnvoyEntrypoint.Command{UI: ui}, nil
},
}
}

Expand Down
23 changes: 23 additions & 0 deletions subcommand/envoy-entrypoint/command_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Package envoyentrypoint
//
// This is intended to be used a Docker entrypoint for Envoy:
// * Run Envoy in a subprocess
// * Forward all signals to the subprocess, except for SIGTERM
// * Monitor task metadata to terminate Envoy after application container(s) stop
package envoyentrypoint

import (
"github.com/hashicorp/go-hclog"
)

func (c *Command) init() {
c.log = hclog.New(&hclog.LoggerOptions{Name: "consul-ecs"})
}

func (c *Command) Help() string {
return ""
}

func (c *Command) Synopsis() string {
return "Entrypoint for running Envoy in ECS"
}
98 changes: 98 additions & 0 deletions subcommand/envoy-entrypoint/command_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//go:build !windows
// +build !windows

package envoyentrypoint

import (
"context"
"os"
"os/signal"
"sync"
"syscall"

"github.com/hashicorp/go-hclog"
"github.com/mitchellh/cli"
)

type Command struct {
UI cli.Ui
log hclog.Logger
once sync.Once

sigs chan os.Signal
ctx context.Context
cancel context.CancelFunc
envoyCmd *EnvoyCmd
appMonitor *AppContainerMonitor
}

func (c *Command) Run(args []string) int {
pglass marked this conversation as resolved.
Show resolved Hide resolved
c.once.Do(c.init)

if len(args) == 0 {
c.UI.Error("command is required")
return 1
}

c.sigs = make(chan os.Signal, 1)
c.ctx, c.cancel = context.WithCancel(context.Background())
c.envoyCmd = NewEnvoyCmd(c.log, args)
c.appMonitor = NewAppContainerMonitor(c.log, c.ctx)

return c.realRun()
}

func (c *Command) realRun() int {
signal.Notify(c.sigs)
defer c.cleanup()

// Run Envoy in the background.
go c.envoyCmd.Run()
// The appMonitor wakes up on SIGTERM to poll task metadata. It is done
// when the application container(s) stop, or when it is cancelled.
go c.appMonitor.Run()

// Wait for Envoy to start.
if _, ok := <-c.envoyCmd.Started(); !ok {
c.log.Error("Envoy failed to start")
return 1
}

for {
select {
case <-c.envoyCmd.Done():
// When the Envoy process exits, we're done.
return c.envoyCmd.ProcessState.ExitCode()
case sig := <-c.sigs:
c.handleSignal(sig)
case _, ok := <-c.appMonitor.Done():
// When the application containers stop (after SIGTERM), tell Envoy to exit.
if ok {
c.log.Info("terminating Envoy with sigterm")
// A negative pid signals the process group to exit, to try to clean up subprocesses as well
if err := syscall.Kill(-c.envoyCmd.Process.Pid, syscall.SIGTERM); err != nil {
c.log.Warn("sending sigterm to Envoy", "error", err.Error())
}
}
}
}
}

func (c *Command) handleSignal(sig os.Signal) {
switch sig {
case syscall.SIGTERM, syscall.SIGCHLD, syscall.SIGURG:
return
default:
if err := c.envoyCmd.Process.Signal(sig); err != nil {
c.log.Warn("forwarding signal", "err", err.Error())
}
}
}

func (c *Command) cleanup() {
signal.Stop(c.sigs)
// Cancel background goroutines
c.cancel()
<-c.appMonitor.Done()
<-c.envoyCmd.Done()
}
Loading