Skip to content

Commit

Permalink
新建容器时指定Ip
Browse files Browse the repository at this point in the history
  • Loading branch information
donknap committed Sep 29, 2024
1 parent eba71f9 commit bc71f92
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 47 deletions.
16 changes: 16 additions & 0 deletions app/application/http/controller/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ func (self Network) Create(http *gin.Context) {
return
}

checkIpInSubnet := [][2]string{
{
params.IpGateway, params.IpSubnet,
},
{
params.IpV6Gateway, params.IpV6Subnet,
},
}
for _, item := range checkIpInSubnet {
_, err := function.IpInSubnet(item[0], item[1])
if err != nil {
self.JsonResponseWithError(http, err, 500)
return
}
}

ipAm := &network.IPAM{
Config: []network.IPAMConfig{},
Options: map[string]string{},
Expand Down
27 changes: 26 additions & 1 deletion app/application/http/controller/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,31 @@ func (self Site) CreateByImage(http *gin.Context) {
return
}

checkIpInSubnet := [][2]string{
{
buildParams.IpV4.Address, buildParams.IpV4.Subnet,
},
{
buildParams.IpV4.Gateway, buildParams.IpV4.Subnet,
},
{
buildParams.IpV6.Address, buildParams.IpV6.Subnet,
},
{
buildParams.IpV6.Gateway, buildParams.IpV6.Subnet,
},
}
for _, item := range checkIpInSubnet {
if item[0] == "" {
continue
}
_, err := function.IpInSubnet(item[0], item[1])
if err != nil {
self.JsonResponseWithError(http, err, 500)
return
}
}

for _, itemDefault := range buildParams.VolumesDefault {
for _, item := range buildParams.Volumes {
if item.Dest == itemDefault.Dest {
Expand Down Expand Up @@ -97,7 +122,7 @@ func (self Site) CreateByImage(http *gin.Context) {
// 重新部署,先删掉之前的容器
if params.Id != 0 || params.ContainerId != "" {
_ = notice.Message{}.Info("containerCreate", "正在停止旧容器")
if oldContainerInfo.ID != "" {
if oldContainerInfo.ContainerJSONBase != nil && oldContainerInfo.ID != "" {
err := docker.Sdk.Client.ContainerStop(docker.Sdk.Ctx, params.SiteName, container.StopOptions{})
if err != nil {
self.JsonResponseWithError(http, err, 500)
Expand Down
75 changes: 60 additions & 15 deletions app/application/logic/docker-container-task.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,38 @@ func (self DockerTask) ContainerCreate(task *CreateContainerOption) (string, err
builder.WithContainerName(task.SiteName)

// 如果绑定了ipv6 需要先创建一个ipv6的自身网络
if task.BuildParams.BindIpV6 || !function.IsEmptyArray(task.BuildParams.Links) {
builder.CreateOwnerNetwork(task.BuildParams.BindIpV6)
// 如果容器配置了Ip,需要先创一个自身网络
if task.BuildParams.BindIpV6 ||
!function.IsEmptyArray(task.BuildParams.Links) ||
task.BuildParams.IpV4.Address != "" || task.BuildParams.IpV6.Address != "" {

option := network.CreateOptions{
IPAM: &network.IPAM{
Driver: "default",
Options: map[string]string{},
Config: []network.IPAMConfig{},
},
}
if task.BuildParams.BindIpV6 {
option.EnableIPv6 = function.PtrBool(true)
}
if task.BuildParams.IpV4.Address != "" {
option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{
Subnet: task.BuildParams.IpV4.Subnet,
Gateway: task.BuildParams.IpV4.Gateway,
})
}
if task.BuildParams.IpV6.Address != "" {
option.EnableIPv6 = function.PtrBool(true)
option.IPAM.Config = append(option.IPAM.Config, network.IPAMConfig{
Subnet: task.BuildParams.IpV6.Subnet,
Gateway: task.BuildParams.IpV6.Gateway,
})
}
err := builder.CreateOwnerNetwork(option)
if err != nil {
return "", err
}
}

// Environment
Expand All @@ -31,13 +61,10 @@ func (self DockerTask) ContainerCreate(task *CreateContainerOption) (string, err
}
}

// Links
// Links Volume
// 避免其它容器先抢占了本身容器配置的ip,需要在容器都完成创建后,统一加入网络
if !function.IsEmptyArray(task.BuildParams.Links) {
for _, value := range task.BuildParams.Links {
if value.Alise == "" {
value.Alise = value.Name
}
builder.WithLink(value.Name, value.Alise)
if value.Volume {
builder.WithContainerVolume(value.Name)
}
Expand Down Expand Up @@ -150,13 +177,36 @@ func (self DockerTask) ContainerCreate(task *CreateContainerOption) (string, err
return "", err
}

err = docker.Sdk.Client.ContainerStart(docker.Sdk.Ctx, response.ID, container.StartOptions{})
if err != nil {
//notice.Message{}.Error("containerCreate", err.Error())
return response.ID, err
}

// 仅当容器有关联时,才加新建自己的网络。对于ipv6支持,必须加入一个ipv6的网络
if task.BuildParams.BindIpV6 || !function.IsEmptyArray(task.BuildParams.Links) {
err = docker.Sdk.Client.NetworkConnect(docker.Sdk.Ctx, task.SiteName, response.ID, &network.EndpointSettings{
if task.BuildParams.BindIpV6 || !function.IsEmptyArray(task.BuildParams.Links) || task.BuildParams.IpV4.Address != "" || task.BuildParams.IpV6.Address != "" {
endpointSetting := &network.EndpointSettings{
Aliases: []string{
fmt.Sprintf("%s.pod.dpanel.local", task.SiteName),
},
})
IPAMConfig: &network.EndpointIPAMConfig{},
}
if task.BuildParams.IpV4.Address != "" {
endpointSetting.IPAMConfig.IPv4Address = task.BuildParams.IpV4.Address
}
if task.BuildParams.IpV6.Address != "" {
endpointSetting.IPAMConfig.IPv6Address = task.BuildParams.IpV6.Address
}
err = docker.Sdk.Client.NetworkConnect(docker.Sdk.Ctx, task.SiteName, response.ID, endpointSetting)
}

if !function.IsEmptyArray(task.BuildParams.Links) {
for _, value := range task.BuildParams.Links {
if value.Alise == "" {
value.Alise = value.Name
}
builder.WithLink(value.Name, value.Alise)
}
}

// 网络需要在创建好容器后统一 connect 否则 bridge 网络会消失。当网络变更后了,可能绑定的端口无法使用。
Expand Down Expand Up @@ -184,11 +234,6 @@ func (self DockerTask) ContainerCreate(task *CreateContainerOption) (string, err
return response.ID, err
}

err = docker.Sdk.Client.ContainerStart(docker.Sdk.Ctx, response.ID, container.StartOptions{})
if err != nil {
//notice.Message{}.Error("containerCreate", err.Error())
return response.ID, err
}
_ = notice.Message{}.Success("containerCreate", task.SiteName)
return response.ID, err
}
59 changes: 34 additions & 25 deletions common/accessor/site_env_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,39 @@ type LogDriverItem struct {
MaxFile string `json:"maxFile"`
MaxSize string `json:"maxSize"`
}

type ContainerNetworkItem struct {
Address string `json:"address"`
Subnet string `json:"subnet"`
Gateway string `json:"gateway"`
}

type SiteEnvOption struct {
Environment []EnvItem `json:"environment"`
Links []LinkItem `json:"links"`
Ports []PortItem `json:"ports"`
Volumes []VolumeItem `json:"volumes"`
VolumesDefault []VolumeItem `json:"volumesDefault"`
Network []NetworkItem `json:"network"`
ImageName string `json:"imageName"` // 非表单提交
ImageId string `json:"imageId"` // 非表单提交
Privileged bool `json:"privileged"`
AutoRemove bool `json:"autoRemove"`
Restart string `json:"restart"`
Cpus float32 `json:"cpus"`
Memory int `json:"memory"`
ShmSize string `json:"shmsize,omitempty"`
WorkDir string `json:"workDir"`
User string `json:"user"`
Command string `json:"command"`
Entrypoint string `json:"entrypoint"`
UseHostNetwork bool `json:"useHostNetwork"`
BindIpV6 bool `json:"bindIpV6"`
Log LogDriverItem `json:"log"`
Dns []string `json:"dns"`
Label []EnvItem `json:"label"`
PublishAllPorts bool `json:"publishAllPorts"`
ExtraHosts []EnvItem `json:"extraHosts"`
Environment []EnvItem `json:"environment"`
Links []LinkItem `json:"links"`
Ports []PortItem `json:"ports"`
Volumes []VolumeItem `json:"volumes"`
VolumesDefault []VolumeItem `json:"volumesDefault"`
Network []NetworkItem `json:"network"`
ImageName string `json:"imageName"` // 非表单提交
ImageId string `json:"imageId"` // 非表单提交
Privileged bool `json:"privileged"`
AutoRemove bool `json:"autoRemove"`
Restart string `json:"restart"`
Cpus float32 `json:"cpus"`
Memory int `json:"memory"`
ShmSize string `json:"shmsize,omitempty"`
WorkDir string `json:"workDir"`
User string `json:"user"`
Command string `json:"command"`
Entrypoint string `json:"entrypoint"`
UseHostNetwork bool `json:"useHostNetwork"`
BindIpV6 bool `json:"bindIpV6"`
Log LogDriverItem `json:"log"`
Dns []string `json:"dns"`
Label []EnvItem `json:"label"`
PublishAllPorts bool `json:"publishAllPorts"`
ExtraHosts []EnvItem `json:"extraHosts"`
IpV4 ContainerNetworkItem `json:"ipV4"`
IpV6 ContainerNetworkItem `json:"ipV6"`
}
21 changes: 21 additions & 0 deletions common/function/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package function

import (
"crypto/md5"
"errors"
"fmt"
"math/rand"
"net"
"path/filepath"
"strings"
)
Expand Down Expand Up @@ -60,3 +62,22 @@ func GetRootPath() string {
rootPath, _ := filepath.Abs("./")
return rootPath
}

func IpInSubnet(ipAddress, subnetAddress string) (bool, error) {
ip := net.ParseIP(ipAddress)
if ip == nil {
return false, errors.New("错误的 ip 地址: " + ipAddress)
}
_, subnet, err := net.ParseCIDR(subnetAddress)
if err != nil {
return false, errors.New("错误的子网 CIDR 地址: " + subnetAddress)
}

if subnetAddress != subnet.String() {
return false, errors.New("错误的子网 CIDR 地址, 应为: " + subnet.String())
}
if !subnet.Contains(ip) {
return false, errors.New("ip 地址与子网地址不匹配")
}
return true, nil
}
24 changes: 20 additions & 4 deletions common/service/docker/container-create.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,18 +234,34 @@ func (self *ContainerCreateBuilder) WithExtraHosts(name, value string) {
self.hostConfig.ExtraHosts = append(self.hostConfig.ExtraHosts, fmt.Sprintf("%s:%s", name, value))
}

func (self *ContainerCreateBuilder) CreateOwnerNetwork(enableIpV6 bool) {
func (self *ContainerCreateBuilder) CreateOwnerNetwork(option network.CreateOptions) error {
// 利用Network关联容器
// 每次创建自身网络时,先删除掉,最后再统一将关联和自身加入进来
// 容器关联时必须采用 hostname 以保证容器可以访问
selfNetwork, err := Sdk.Client.NetworkInspect(Sdk.Ctx, self.containerName, network.InspectOptions{})
if err == nil {
for _, item := range selfNetwork.Containers {
err = Sdk.Client.NetworkDisconnect(Sdk.Ctx, self.containerName, item.Name, true)
}
if err != nil {
return err
}
_ = Sdk.Client.NetworkRemove(Sdk.Ctx, self.containerName)
}
options := make(map[string]string)
options["name"] = self.containerName
_, err := Sdk.Client.NetworkCreate(Sdk.Ctx, self.containerName, network.CreateOptions{

myOption := network.CreateOptions{
Driver: "bridge",
Options: options,
EnableIPv6: &enableIpV6,
})
EnableIPv6: option.EnableIPv6,
IPAM: option.IPAM,
}
_, err = Sdk.Client.NetworkCreate(Sdk.Ctx, self.containerName, myOption)
if err != nil {
slog.Debug("create network", "name", self.containerName, err)
}
return err
}

func (self *ContainerCreateBuilder) Execute() (response container.CreateResponse, err error) {
Expand Down
4 changes: 2 additions & 2 deletions tests/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ func TestCreateContainer(t *testing.T) {
builder.WithImage("portainer/portainer-ce:latest", false)
builder.WithContainerName("portainer")
//builder.WithEnv("PMA_HOST", "localmysql")
builder.WithPort("8000", "8000")
builder.WithPort("9000", "9000")
builder.WithPort("", "8000", "8000")
builder.WithPort("", "9000", "9000")
//builder.WithLink("localmysql", "localmysql")
builder.WithRestart("always")
builder.WithPrivileged()
Expand Down
23 changes: 23 additions & 0 deletions tests/func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,26 @@ func TestSplitCommand(t *testing.T) {
cmdArr = function.CommandSplit(cmd)
asserter.Equal(cmdArr[5], "config.yaml")
}

func TestIpInSubnet(t *testing.T) {
asserter := assert.New(t)
check, err := function.IpInSubnet("192.168.0.1", "192.168.0.0/24")
fmt.Printf("%v \n", err)
asserter.True(check)

check, err = function.IpInSubnet("192.168.0.1", "192.168.1.0/24")
fmt.Printf("%v \n", err)
asserter.False(check)

check, err = function.IpInSubnet("2001:db8::2", "2001:db8::/48")
fmt.Printf("%v \n", err)
asserter.True(check)

check, err = function.IpInSubnet("", "")
fmt.Printf("%v \n", err)
asserter.True(check)

check, err = function.IpInSubnet("192.168.0.1", "192.168.0.1/24")
fmt.Printf("%v \n", err)
asserter.True(check)
}

0 comments on commit bc71f92

Please sign in to comment.