Skip to content

Commit 57aef3b

Browse files
committed
Windows: Support running dockerd as a service
This adds support for Windows dockerd to run as a Windows service, managed by the service control manager. The log is written to the Windows event log (and can be viewed in the event viewer or in PowerShell). If there is a Go panic, the stack is written to a file panic.log in the Docker root. Signed-off-by: John Starks <[email protected]>
1 parent 4925fcb commit 57aef3b

49 files changed

Lines changed: 8069 additions & 69 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/docker/docker_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package main
22

33
import (
4-
_ "github.com/docker/docker/autogen/winresources/dockerd"
4+
_ "github.com/docker/docker/autogen/winresources/docker"
55
)

cmd/dockerd/daemon.go

Lines changed: 55 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type DaemonCli struct {
5353
*daemon.Config
5454
commonFlags *cli.CommonFlags
5555
configFile *string
56+
57+
api *apiserver.Server
58+
d *daemon.Daemon
5659
}
5760

5861
func presentInHelp(usage string) string { return usage }
@@ -121,7 +124,10 @@ func migrateKey() (err error) {
121124
return nil
122125
}
123126

124-
func (cli *DaemonCli) start() {
127+
func (cli *DaemonCli) start() (err error) {
128+
stopc := make(chan bool)
129+
defer close(stopc)
130+
125131
// warn from uuid package when running the daemon
126132
uuid.Loggerf = logrus.Warnf
127133

@@ -133,8 +139,7 @@ func (cli *DaemonCli) start() {
133139
}
134140
cliConfig, err := loadDaemonCliConfig(cli.Config, flags, cli.commonFlags, *cli.configFile)
135141
if err != nil {
136-
fmt.Fprint(os.Stderr, err)
137-
os.Exit(1)
142+
return err
138143
}
139144
cli.Config = cliConfig
140145

@@ -152,24 +157,22 @@ func (cli *DaemonCli) start() {
152157
})
153158

154159
if err := setDefaultUmask(); err != nil {
155-
logrus.Fatalf("Failed to set umask: %v", err)
160+
return fmt.Errorf("Failed to set umask: %v", err)
156161
}
157162

158163
if len(cli.LogConfig.Config) > 0 {
159164
if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil {
160-
logrus.Fatalf("Failed to set log opts: %v", err)
165+
return fmt.Errorf("Failed to set log opts: %v", err)
161166
}
162167
}
163168

164-
var pfile *pidfile.PIDFile
165169
if cli.Pidfile != "" {
166170
pf, err := pidfile.New(cli.Pidfile)
167171
if err != nil {
168-
logrus.Fatalf("Error starting daemon: %v", err)
172+
return fmt.Errorf("Error starting daemon: %v", err)
169173
}
170-
pfile = pf
171174
defer func() {
172-
if err := pfile.Remove(); err != nil {
175+
if err := pf.Remove(); err != nil {
173176
logrus.Error(err)
174177
}
175178
}()
@@ -196,7 +199,7 @@ func (cli *DaemonCli) start() {
196199
}
197200
tlsConfig, err := tlsconfig.Server(tlsOptions)
198201
if err != nil {
199-
logrus.Fatal(err)
202+
return err
200203
}
201204
serverConfig.TLSConfig = tlsConfig
202205
}
@@ -210,13 +213,13 @@ func (cli *DaemonCli) start() {
210213
for i := 0; i < len(cli.Config.Hosts); i++ {
211214
var err error
212215
if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
213-
logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
216+
return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
214217
}
215218

216219
protoAddr := cli.Config.Hosts[i]
217220
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
218221
if len(protoAddrParts) != 2 {
219-
logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
222+
return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
220223
}
221224

222225
proto := protoAddrParts[0]
@@ -228,37 +231,32 @@ func (cli *DaemonCli) start() {
228231
}
229232
l, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
230233
if err != nil {
231-
logrus.Fatal(err)
234+
return err
232235
}
233236
// If we're binding to a TCP port, make sure that a container doesn't try to use it.
234237
if proto == "tcp" {
235238
if err := allocateDaemonPort(addr); err != nil {
236-
logrus.Fatal(err)
239+
return err
237240
}
238241
}
239242
logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
240243
api.Accept(protoAddrParts[1], l...)
241244
}
242245

243246
if err := migrateKey(); err != nil {
244-
logrus.Fatal(err)
247+
return err
245248
}
246249
cli.TrustKeyPath = cli.commonFlags.TrustKey
247250

248251
registryService := registry.NewService(cli.Config.ServiceOptions)
249252
containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)
250253
if err != nil {
251-
logrus.Fatal(err)
254+
return err
252255
}
253256

254257
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
255258
if err != nil {
256-
if pfile != nil {
257-
if err := pfile.Remove(); err != nil {
258-
logrus.Error(err)
259-
}
260-
}
261-
logrus.Fatalf("Error starting daemon: %v", err)
259+
return fmt.Errorf("Error starting daemon: %v", err)
262260
}
263261

264262
logrus.Info("Daemon has completed initialization")
@@ -272,26 +270,9 @@ func (cli *DaemonCli) start() {
272270
cli.initMiddlewares(api, serverConfig)
273271
initRouter(api, d)
274272

275-
reload := func(config *daemon.Config) {
276-
if err := d.Reload(config); err != nil {
277-
logrus.Errorf("Error reconfiguring the daemon: %v", err)
278-
return
279-
}
280-
if config.IsValueSet("debug") {
281-
debugEnabled := utils.IsDebugEnabled()
282-
switch {
283-
case debugEnabled && !config.Debug: // disable debug
284-
utils.DisableDebug()
285-
api.DisableProfiler()
286-
case config.Debug && !debugEnabled: // enable debug
287-
utils.EnableDebug()
288-
api.EnableProfiler()
289-
}
290-
291-
}
292-
}
293-
294-
setupConfigReloadTrap(*cli.configFile, flags, reload)
273+
cli.d = d
274+
cli.api = api
275+
cli.setupConfigReloadTrap()
295276

296277
// The serve API routine never exits unless an error occurs
297278
// We need to start it as a goroutine and wait on it so
@@ -300,14 +281,8 @@ func (cli *DaemonCli) start() {
300281
go api.Wait(serveAPIWait)
301282

302283
signal.Trap(func() {
303-
api.Close()
304-
<-serveAPIWait
305-
shutdownDaemon(d, 15)
306-
if pfile != nil {
307-
if err := pfile.Remove(); err != nil {
308-
logrus.Error(err)
309-
}
310-
}
284+
cli.stop()
285+
<-stopc // wait for daemonCli.start() to return
311286
})
312287

313288
// after the daemon is done setting up we can notify systemd api
@@ -319,13 +294,39 @@ func (cli *DaemonCli) start() {
319294
shutdownDaemon(d, 15)
320295
containerdRemote.Cleanup()
321296
if errAPI != nil {
322-
if pfile != nil {
323-
if err := pfile.Remove(); err != nil {
324-
logrus.Error(err)
297+
return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
298+
}
299+
300+
return nil
301+
}
302+
303+
func (cli *DaemonCli) reloadConfig() {
304+
reload := func(config *daemon.Config) {
305+
if err := cli.d.Reload(config); err != nil {
306+
logrus.Errorf("Error reconfiguring the daemon: %v", err)
307+
return
308+
}
309+
if config.IsValueSet("debug") {
310+
debugEnabled := utils.IsDebugEnabled()
311+
switch {
312+
case debugEnabled && !config.Debug: // disable debug
313+
utils.DisableDebug()
314+
cli.api.DisableProfiler()
315+
case config.Debug && !debugEnabled: // enable debug
316+
utils.EnableDebug()
317+
cli.api.EnableProfiler()
325318
}
319+
326320
}
327-
logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI)
328321
}
322+
323+
if err := daemon.ReloadConfiguration(*cli.configFile, flag.CommandLine, reload); err != nil {
324+
logrus.Error(err)
325+
}
326+
}
327+
328+
func (cli *DaemonCli) stop() {
329+
cli.api.Close()
329330
}
330331

331332
// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case

cmd/dockerd/daemon_unix.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ import (
1111
"strconv"
1212
"syscall"
1313

14-
"github.com/Sirupsen/logrus"
1514
"github.com/docker/docker/daemon"
1615
"github.com/docker/docker/libcontainerd"
17-
"github.com/docker/docker/pkg/mflag"
1816
"github.com/docker/docker/pkg/system"
1917
"github.com/docker/libnetwork/portallocator"
2018
)
@@ -49,14 +47,12 @@ func getDaemonConfDir() string {
4947
}
5048

5149
// setupConfigReloadTrap configures the USR2 signal to reload the configuration.
52-
func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
50+
func (cli *DaemonCli) setupConfigReloadTrap() {
5351
c := make(chan os.Signal, 1)
5452
signal.Notify(c, syscall.SIGHUP)
5553
go func() {
5654
for range c {
57-
if err := daemon.ReloadConfiguration(configFile, flags, reload); err != nil {
58-
logrus.Error(err)
59-
}
55+
cli.reloadConfig()
6056
}
6157
}()
6258
}
@@ -111,3 +107,7 @@ func allocateDaemonPort(addr string) error {
111107
}
112108
return nil
113109
}
110+
111+
// notifyShutdown is called after the daemon shuts down but before the process exits.
112+
func notifyShutdown(err error) {
113+
}

cmd/dockerd/daemon_windows.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import (
66
"syscall"
77

88
"github.com/Sirupsen/logrus"
9-
"github.com/docker/docker/daemon"
109
"github.com/docker/docker/libcontainerd"
11-
"github.com/docker/docker/pkg/mflag"
1210
"github.com/docker/docker/pkg/system"
1311
)
1412

@@ -31,10 +29,23 @@ func getDaemonConfDir() string {
3129

3230
// notifySystem sends a message to the host when the server is ready to be used
3331
func notifySystem() {
32+
if service != nil {
33+
err := service.started()
34+
if err != nil {
35+
logrus.Fatal(err)
36+
}
37+
}
38+
}
39+
40+
// notifyShutdown is called after the daemon shuts down but before the process exits.
41+
func notifyShutdown(err error) {
42+
if service != nil {
43+
service.stopped(err)
44+
}
3445
}
3546

3647
// setupConfigReloadTrap configures a Win32 event to reload the configuration.
37-
func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
48+
func (cli *DaemonCli) setupConfigReloadTrap() {
3849
go func() {
3950
sa := syscall.SecurityAttributes{
4051
Length: 0,
@@ -44,9 +55,7 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(
4455
logrus.Debugf("Config reload - waiting signal at %s", ev)
4556
for {
4657
syscall.WaitForSingleObject(h, syscall.INFINITE)
47-
if err := daemon.ReloadConfiguration(configFile, flags, reload); err != nil {
48-
logrus.Error(err)
49-
}
58+
cli.reloadConfig()
5059
}
5160
}
5261
}()

cmd/dockerd/docker.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,21 @@ func main() {
5656
flag.Usage()
5757
return
5858
}
59-
daemonCli.start()
59+
60+
// On Windows, this may be launching as a service or with an option to
61+
// register the service.
62+
stop, err := initService()
63+
if err != nil {
64+
logrus.Fatal(err)
65+
}
66+
67+
if !stop {
68+
err = daemonCli.start()
69+
notifyShutdown(err)
70+
if err != nil {
71+
logrus.Fatal(err)
72+
}
73+
}
6074
}
6175

6276
func showVersion() {

cmd/dockerd/docker_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package main
22

33
import (
4-
_ "github.com/docker/docker/autogen/winresources/docker"
4+
_ "github.com/docker/docker/autogen/winresources/dockerd"
55
)

cmd/dockerd/service_unsupported.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build !windows
2+
3+
package main
4+
5+
func initService() (bool, error) {
6+
return false, nil
7+
}

0 commit comments

Comments
 (0)