mirror of https://github.com/docker/compose.git
Handle service scale with container numbering
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
da99ad40d5
commit
2278370ffa
|
@ -73,3 +73,4 @@ func (s *local) ResourceService() resources.Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -169,13 +171,42 @@ func (s *local) Down(ctx context.Context, projectName string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
w := progress.ContextWriter(ctx)
|
||||||
for _, c := range list {
|
for _, c := range list {
|
||||||
err := s.containerService.Stop(ctx, c.ID, nil)
|
container := c
|
||||||
|
eg.Go(func() error {
|
||||||
|
w.Event(progress.Event{
|
||||||
|
ID: getContainerName(container),
|
||||||
|
Text: "Stopping",
|
||||||
|
Status: progress.Working,
|
||||||
|
Done: false,
|
||||||
|
})
|
||||||
|
err := s.containerService.Stop(ctx, container.ID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
w.Event(progress.Event{
|
||||||
|
ID: getContainerName(container),
|
||||||
|
Text: "Removing",
|
||||||
|
Status: progress.Working,
|
||||||
|
Done: false,
|
||||||
|
})
|
||||||
|
err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
w.Event(progress.Event{
|
||||||
|
ID: getContainerName(container),
|
||||||
|
Text: "Removed",
|
||||||
|
Status: progress.Done,
|
||||||
|
Done: true,
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error {
|
func (s *local) Logs(ctx context.Context, projectName string, w io.Writer) error {
|
||||||
|
@ -250,7 +281,7 @@ func (s *local) Convert(ctx context.Context, project *types.Project, format stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
||||||
hash, err := jsonHash(s)
|
hash, err := jsonHash(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
@ -259,6 +290,7 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, inherit
|
||||||
"com.docker.compose.project": p.Name,
|
"com.docker.compose.project": p.Name,
|
||||||
"com.docker.compose.service": s.Name,
|
"com.docker.compose.service": s.Name,
|
||||||
"com.docker.compose.config-hash": hash,
|
"com.docker.compose.config-hash": hash,
|
||||||
|
"com.docker.compose.container-number": strconv.Itoa(number),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -523,7 +555,7 @@ func (s *local) ensureNetwork(ctx context.Context, n types.NetworkConfig) error
|
||||||
}
|
}
|
||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
ID: fmt.Sprintf("Network %q", n.Name),
|
ID: fmt.Sprintf("Network %q", n.Name),
|
||||||
Status: progress.Working,
|
Status: progress.Done,
|
||||||
StatusText: "Created",
|
StatusText: "Created",
|
||||||
Done: true,
|
Done: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,14 +21,14 @@ package local
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/containers"
|
"github.com/docker/compose-cli/api/containers"
|
||||||
"github.com/docker/compose-cli/progress"
|
"github.com/docker/compose-cli/progress"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
||||||
|
@ -42,30 +42,90 @@ func (s *local) ensureService(ctx context.Context, project *types.Project, servi
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scale := getScale(service)
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
if len(actual) < scale {
|
||||||
|
next, err := nextContainerNumber(actual)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
missing := scale - len(actual)
|
||||||
|
for i := 0; i < missing; i++ {
|
||||||
|
number := next + i
|
||||||
|
name := fmt.Sprintf("%s_%s_%d", project.Name, service.Name, number)
|
||||||
|
eg.Go(func() error {
|
||||||
|
return s.createContainer(ctx, project, service, name, number)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actual) > scale {
|
||||||
|
for i := scale; i < len(actual); i++ {
|
||||||
|
container := actual[i]
|
||||||
|
eg.Go(func() error {
|
||||||
|
err := s.containerService.Stop(ctx, container.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
actual = actual[:scale]
|
||||||
|
}
|
||||||
|
|
||||||
expected, err := jsonHash(service)
|
expected, err := jsonHash(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, container := range actual {
|
||||||
if len(actual) == 0 {
|
container := container
|
||||||
return s.createContainer(ctx, project, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
container := actual[0] // TODO handle services with replicas
|
|
||||||
diverged := container.Labels["com.docker.compose.config-hash"] != expected
|
diverged := container.Labels["com.docker.compose.config-hash"] != expected
|
||||||
if diverged {
|
if diverged {
|
||||||
|
eg.Go(func() error {
|
||||||
return s.recreateContainer(ctx, project, service, container)
|
return s.recreateContainer(ctx, project, service, container)
|
||||||
|
})
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State == "running" {
|
if container.State == "running" {
|
||||||
// already running, skip
|
// already running, skip
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
return s.restartContainer(ctx, service, container)
|
return s.restartContainer(ctx, service, container)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
|
func nextContainerNumber(containers []moby.Container) (int, error) {
|
||||||
|
max := 0
|
||||||
|
for _, c := range containers {
|
||||||
|
n, err := strconv.Atoi(c.Labels["com.docker.compose.container-number"])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n > max {
|
||||||
|
max = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max + 1, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getScale(config types.ServiceConfig) int {
|
||||||
|
if config.Deploy != nil && config.Deploy.Replicas != nil {
|
||||||
|
return int(*config.Deploy.Replicas)
|
||||||
|
}
|
||||||
|
if config.Scale != 0 {
|
||||||
|
return config.Scale
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *local) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error {
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
ID: fmt.Sprintf("Service %q", service.Name),
|
ID: fmt.Sprintf("Service %q", service.Name),
|
||||||
|
@ -73,8 +133,7 @@ func (s *local) createContainer(ctx context.Context, project *types.Project, ser
|
||||||
StatusText: "Create",
|
StatusText: "Create",
|
||||||
Done: false,
|
Done: false,
|
||||||
})
|
})
|
||||||
name := fmt.Sprintf("%s_%s", project.Name, service.Name)
|
err := s.runContainer(ctx, project, service, name, number, nil)
|
||||||
err := s.runContainer(ctx, project, service, name, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -105,7 +164,11 @@ func (s *local) recreateContainer(ctx context.Context, project *types.Project, s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.runContainer(ctx, project, service, name, &container)
|
number, err := strconv.Atoi(container.Labels["com.docker.compose.container-number"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.runContainer(ctx, project, service, name, number, &container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -143,8 +206,8 @@ func (s *local) restartContainer(ctx context.Context, service types.ServiceConfi
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, container *moby.Container) error {
|
func (s *local) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container) error {
|
||||||
containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, container)
|
containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, number, container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue