// restoreContainers loads all running containers and inserts // them into skydns when skydock starts func restoreContainers() error { containers, err := dockerClient.FetchAllContainers() if err != nil { return err } var container *docker.Container for _, cnt := range containers { uuid := utils.Truncate(cnt.Id) if container, err = dockerClient.FetchContainer(uuid, cnt.Image); err != nil { if err != docker.ErrImageNotTagged { log.Logf(log.ERROR, "failed to fetch %s on restore: %s", cnt.Id, err) } continue } service, err := plugins.createService(container) if err != nil { // doing a fatal here because we cannot do much if the plugins // return an invalid service or error fatal(err) } if err := sendService(uuid, service); err != nil { log.Logf(log.ERROR, "failed to send %s to skydns on restore: %s", uuid, err) } } return nil }
// sendService sends the uuid and service data to skydns func sendService(uuid string, service *msg.Service) error { log.Logf(log.INFO, "adding %s (%s) to skydns", uuid, service.Name) if err := skydns.Add(uuid, service); err != nil { // ignore erros for conflicting uuids and start the heartbeat again if err != client.ErrConflictingUUID { return err } log.Logf(log.INFO, "service already exists for %s. Resetting ttl.", uuid) updateService(uuid, ttl) } go heartbeat(uuid) return nil }
func main() { validateSettings() if err := setupLogger(); err != nil { fatal(err) } var ( err error group = &sync.WaitGroup{} ) plugins, err = newRuntime(pluginFile) if err != nil { fatal(err) } if dockerClient, err = docker.NewClient(pathToSocket); err != nil { log.Logf(log.FATAL, "error connecting to docker: %s", err) fatal(err) } if skydnsContainerName != "" { container, err := dockerClient.FetchContainer(skydnsContainerName, "") if err != nil { log.Logf(log.FATAL, "error retrieving skydns container '%s': %s", skydnsContainerName, err) fatal(err) } skydnsUrl = "http://" + container.NetworkSettings.IpAddress + ":8080" } log.Logf(log.INFO, "skydns URL: %s", skydnsUrl) if skydns, err = client.NewClient(skydnsUrl, secret, domain, "172.17.42.1:53"); err != nil { log.Logf(log.FATAL, "error connecting to skydns: %s", err) fatal(err) } log.Logf(log.DEBUG, "starting restore of containers") if err := restoreContainers(); err != nil { log.Logf(log.FATAL, "error restoring containers: %s", err) fatal(err) } events := dockerClient.GetEvents() group.Add(numberOfHandlers) // Start event handlers for i := 0; i < numberOfHandlers; i++ { go eventHandler(events, group) } log.Logf(log.DEBUG, "starting main process") group.Wait() log.Logf(log.DEBUG, "stopping cleanly via EOF") }
func eventHandler(c chan *docker.Event, group *sync.WaitGroup) { defer group.Done() for event := range c { log.Logf(log.DEBUG, "received event (%s) %s %s", event.Status, event.ContainerId, event.Image) uuid := utils.Truncate(event.ContainerId) switch event.Status { case "die", "stop", "kill": if err := removeService(uuid); err != nil { log.Logf(log.ERROR, "error removing %s from skydns: %s", uuid, err) } case "start", "restart": if err := addService(uuid, event.Image); err != nil { log.Logf(log.ERROR, "error adding %s to skydns: %s", uuid, err) } } } }
func heartbeat(uuid string) { runningLock.Lock() if _, exists := running[uuid]; exists { runningLock.Unlock() return } running[uuid] = struct{}{} runningLock.Unlock() defer func() { runningLock.Lock() delete(running, uuid) runningLock.Unlock() }() var errorCount int for _ = range time.Tick(time.Duration(beat) * time.Second) { if errorCount > 10 { // if we encountered more than sequential 10 errors just quit log.Logf(log.ERROR, "aborting heartbeat for %s after sequential 10 errors", uuid) return } // don't fill logs if we have a low beat // may need to do something better here if beat >= 30 { log.Logf(log.INFO, "updating ttl for %s", uuid) } if err := updateService(uuid, ttl); err != nil { errorCount++ log.Logf(log.ERROR, "updateService(): %s", err) } else { if errorCount--; errorCount < 0 { errorCount = 0 } } } }
func (d *dockerClient) GetEvents() chan *Event { eventChan := make(chan *Event, 100) // 100 event buffer go func() { defer close(eventChan) c, err := d.newConn() if err != nil { log.Logf(log.FATAL, "cannot connect to docker: %s", err) return } defer c.Close() req, err := http.NewRequest("GET", "/events", nil) if err != nil { log.Logf(log.ERROR, "bad request for events: %s", err) return } resp, err := c.Do(req) if err != nil { log.Logf(log.FATAL, "cannot connect to events endpoint: %s", err) return } defer resp.Body.Close() // handle signals to stop the socket sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { for sig := range sigChan { log.Logf(log.INFO, "received signal '%v', exiting", sig) c.Close() close(eventChan) os.Exit(0) } }() dec := json.NewDecoder(resp.Body) for { var event *Event if err := dec.Decode(&event); err != nil { if err == io.EOF { break } log.Logf(log.ERROR, "cannot decode json: %s", err) continue } eventChan <- event } log.Logf(log.DEBUG, "closing event channel") }() return eventChan }
func newRuntime(file string) (*pluginRuntime, error) { runtime := otto.New() log.Logf(log.INFO, "loading plugins from %s", file) content, err := ioutil.ReadFile(file) if err != nil { return nil, err } if _, err := runtime.Run(string(content)); err != nil { return nil, err } if err := loadDefaults(runtime); err != nil { return nil, err } return &pluginRuntime{runtime}, nil }
func removeService(uuid string) error { log.Logf(log.INFO, "removing %s from skydns", uuid) return skydns.Delete(uuid) }