def start_supervisor(self): """ Starts the container and runs Supervisor inside the container. """ command = '/usr/bin/supervisord -n' self.logger.info("Starting process supervisor (and SSH server) ..") # Select the Docker image to use as a base for the container. image = self.find_image(self.image) or self.find_image(self.base) self.logger.verbose("Creating container from image: %r", image) # Start the container with the given command. result = self.client.create_container(image=image.unique_name, command=command, hostname=self.hostname, ports=['22']) container_ids = [c['Id'] for c in self.client.containers(all=True)] self.session.container_id = self.expand_id(result['Id'], container_ids) self.logger.verbose("Created container: %s", summarize_id(self.session.container_id)) for text in result.get('Warnings', []): logger.warn("%s", text) # Start the command inside the container. self.logger.verbose("Running command: %s", command) self.client.start(self.session.container_id) # Make the output from the container visible to the user. self.session.remote_terminal = RemoteTerminal(self.session.container_id) self.session.remote_terminal.attach() # Persist association between (repository, tag) and container id. with self.config as state: state['containers'][self.image.key] = self.session.container_id
def __repr__(self): """ Provide a textual representation of an :py:class:`Image` object. """ properties = ["repository=%r" % self.repository, "tag=%r" % self.tag] if self.id: properties.append("id=%r" % summarize_id(self.id)) return "Image(%s)" % ", ".join(properties)
def ssh_endpoint(self): """ Wait for the container to become reachable over SSH_ and get a tuple with the IP address and port number that can be used to connect to the container over SSH. """ self.check_active() if self.session.ssh_endpoint: return self.session.ssh_endpoint # Get the local port connected to the container. host_port = int(self.client.port(self.session.container_id, '22')) self.logger.debug("Configured port redirection for container %s: %s:%i -> %s:%i", summarize_id(self.session.container_id), socket.gethostname(), host_port, self.hostname, 22) # Give the container time to finish the SSH server installation. self.logger.verbose("Waiting for SSH connection to %s (max %i seconds) ..", summarize_id(self.session.container_id), self.timeout) global_timeout = time.time() + self.timeout ssh_timer = humanfriendly.Timer() while time.time() < global_timeout: for ip_address in find_local_ip_addresses(): # Try to open an SSH connection to the container. self.logger.debug("Connecting to container over SSH at %s:%s ..", ip_address, host_port) command = self.get_ssh_client_command(ip_address, host_port) + ['true'] ssh_client = subprocess.Popen(command, stdin=open(os.devnull), stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Give this attempt at most 10 seconds to succeed. inner_timeout = time.time() + 10 while time.time() < inner_timeout: if ssh_client.poll() is not None: break time.sleep(0.1) else: self.logger.debug("Attempt to connect timed out!") if ssh_client.returncode == 0: # At this point we have successfully connected! self.session.ssh_endpoint = (ip_address, host_port) self.logger.debug("Connected to %s at %s using SSH in %s.", self.image.name, self.session.ssh_endpoint, ssh_timer) return self.session.ssh_endpoint time.sleep(1) msg = "Time ran out while waiting to connect to container %s over SSH! (Most likely something went wrong while initializing the container..)" raise SecureShellTimeout, msg % self.image.name
def find_container(self): """ Check to see if the current :py:class:`Container` has an associated Docker container that is currently running. :returns: ``True`` when a running container exists, ``False`` otherwise. """ if not self.session.container_id: self.logger.verbose("Looking for running container ..") state = self.config.load() container_id = state['containers'].get(self.image.key) # Make sure the container is still running. if container_id in [c['Id'] for c in self.client.containers()]: self.session.container_id = container_id self.logger.info("Found running container: %s", summarize_id(container_id)) return bool(self.session.container_id)
def find_base_image(client): """ Find the id of the base image that's used by Redock to create new containers. If the image doesn't exist yet it will be created using :py:func:`create_base_image()`. :param client: Connection to Docker (instance of :py:class:`docker.Client`) :returns: The unique id of the base image. """ logger.verbose("Looking for base image ..") image_id = find_named_image(client, BASE_IMAGE_REPO, BASE_IMAGE_TAG) if image_id: logger.verbose("Found base image: %s", summarize_id(image_id)) return image_id else: logger.verbose("No base image found, creating it ..") return create_base_image(client)
def create_base_image(client): """ Create the base image that's used by Redock to create new containers. This base image differs from the ubuntu:precise_ image (on which it is based) on a couple of points: - Automatic installation of recommended packages is disabled to conserve disk space. - The Ubuntu package mirror is set to a geographically close location to speed up downloading of system packages (see :py:func:`redock.utils.select_ubuntu_mirror()`). - The package list is updated to make sure apt-get_ installs the most up to date packages. - The following system packages are installed: language-pack-en-base_ In a base Docker Ubuntu 12.04 image lots of commands complain loudly about the locale_. This silences the warnings by fixing the problem (if you want to call it that). openssh-server_ After creating a new container Redock will connect to it over SSH_, so having an SSH server installed is a good start :-) supervisor_ The base Docker Ubuntu 12.04 image has init_ (upstart_) disabled. Indeed we don't need all of the bagage that comes with init but it is nice to have a process runner for the SSH_ server (and eventually maybe more). - The initscripts_ and upstart_ system packages are marked 'on hold' so that apt-get_ will not upgrade them. This makes it possible to run ``apt-get dist-upgrade`` inside containers. - An SSH_ key pair is generated and the SSH public key is installed inside the base image so that Redock can connect to the container over SSH (you need ssh-keygen_ installed). - Supervisor_ is configured to automatically start the SSH_ server. :param client: Connection to Docker (instance of :py:class:`docker.Client`) :returns: The unique id of the base image. .. _apt-get: http://manpages.ubuntu.com/manpages/precise/man8/apt-get.8.html .. _init: http://manpages.ubuntu.com/manpages/precise/man8/init.8.html .. _initscripts: http://packages.ubuntu.com/precise/initscripts .. _language-pack-en-base: http://packages.ubuntu.com/precise/language-pack-en-base .. _locale: http://en.wikipedia.org/wiki/Locale .. _openssh-server: http://packages.ubuntu.com/precise/openssh-server .. _ssh-keygen: http://manpages.ubuntu.com/manpages/precise/man1/ssh-keygen.1.html .. _supervisor: http://packages.ubuntu.com/precise/supervisor .. _ubuntu:precise: https://index.docker.io/_/ubuntu/ .. _upstart: http://packages.ubuntu.com/precise/upstart """ download_image(client, 'ubuntu', 'precise') creation_timer = Timer() logger.info("Initializing base image (this can take a few minutes but you only have to do it once) ..") command = ' && '.join([ 'echo %s > /etc/apt/apt.conf.d/90redock' % pipes.quote(APT_CONFIG.strip()), 'echo %s > /etc/apt/sources.list' % pipes.quote(SOURCES_LIST.format(mirror=select_ubuntu_mirror()).strip()), 'apt-get update', 'DEBIAN_FRONTEND=noninteractive apt-get install -q -y language-pack-en-base openssh-server supervisor', 'apt-get clean', # Don't keep the +/- 20 MB of *.deb archives after installation. # Make it possible to run `apt-get dist-upgrade'. # https://help.ubuntu.com/community/PinningHowto#Introduction_to_Holding_Packages 'apt-mark hold initscripts upstart', # Install the generated SSH public key. 'mkdir -p /root/.ssh', 'echo %s > /root/.ssh/authorized_keys' % pipes.quote(get_ssh_public_key()), # Create the Supervisor configuration for the SSH server. 'echo %s > /etc/supervisor/conf.d/ssh-server.conf' % pipes.quote(SUPERVISOR_CONFIG.strip())]) logger.debug("Generated command line: %s", command) result = client.create_container(image='ubuntu:precise', command='bash -c %s' % pipes.quote(command), hostname='redock-template', ports=['22']) container_id = result['Id'] for text in result.get('Warnings', []): logger.warn("%s", text) logger.verbose("Created container %s.", summarize_id(container_id)) client.start(container_id) with RemoteTerminal(container_id): logger.info("Waiting for initialization to finish ..") client.wait(container_id) logger.info("Finished initialization in %s.", creation_timer) commit_timer = Timer() logger.info("Saving initialized container as new base image ..") result = client.commit(container_id, repository='redock', tag='base') logger.info("Done! Committed base image as %s in %s.", summarize_id(result['Id']), commit_timer) return result['Id']