From d9701c4b51930165247c747a50d00283a154b71f Mon Sep 17 00:00:00 2001 From: John Mulhausen Date: Tue, 11 Oct 2016 01:00:32 -0700 Subject: [PATCH] Fix for 404 bg object, right-nav --- css/documentation.css | 1 - getting-started/index.md | 22 +++--- getting-started/part2.md | 142 +++++++++++++++++++++++---------------- getting-started/part3.md | 88 +++++++++++++----------- getting-started/part4.md | 15 +++-- js/menu.js | 6 +- 6 files changed, 157 insertions(+), 117 deletions(-) diff --git a/css/documentation.css b/css/documentation.css index 2fdab54e80..ef58de1519 100644 --- a/css/documentation.css +++ b/css/documentation.css @@ -336,7 +336,6 @@ color: #F04124; border: 0; text-indent: -9999px; background-color: transparent; - background-image: url("https://blog.docker.com/wp-content/themes/whale_roots/assets/img/search-icon.png"); /* background-size: 38px 38px; */ background-repeat: no-repeat; background-position: center center; diff --git a/getting-started/index.md b/getting-started/index.md index 33fc11e7b5..7ffc498579 100644 --- a/getting-started/index.md +++ b/getting-started/index.md @@ -10,24 +10,19 @@ title: "Getting Started, Part 1: Orientation and Setup" This tutorial will create a simple application that runs in a cluster, so you get a sense of how to build distributed applications with the Docker platform. +We will achieve this in the following steps: -1. In part one, which you're reading now, we get set up and oriented. -2. In part two, we create a "Hello World" application that identifies itself. -3. In part three, we hook up a visitor counter. -4. In part four, we show how to scale this "Hello World" app as if it were very - high traffic, by setting up a cluster. -5. In part five, we show how to manage our cluster with a graphical user - interface tool, and roll out code updates. +1. Get set up and oriented, on this page. +2. [Create a "Hello World" application that identifies its environment](part2.md) +3. [Hook up a visitor counter](part3.md) +4. [Scale our app as if it were very high traffic, by setting up a cluster in + production](part4.md) The application itself is very simple so that you are not too distracted by what the code is doing. After all, the value of Docker is in how it can build, ship, and run applications; it's totally agnostic as to what your application actually does. -By the end of this tutorial, you should have a good sense of how the entire -platform works, from setting up your dev environment, running and testing your -code, and finally, building, deploying, and managing your application. - ## Setup Before we get started, make sure your system has the latest version of Docker @@ -35,6 +30,11 @@ installed. [Install Docker](/engine/installation/index.md){: class="button darkblue-btn"} +> Note: If you're in Linux, you'll want to install + [Docker Toolbox](../toolbox/index.md) so you get Docker Compose. + +## Let's go! + If you understand that container images package application code and their dependencies all together in a portable deliverable, and your environment has Docker installed, let's move on! diff --git a/getting-started/part2.md b/getting-started/part2.md index 3380178a91..f8ef7825d4 100644 --- a/getting-started/part2.md +++ b/getting-started/part2.md @@ -13,26 +13,28 @@ In this section, you will write, build, run, and share an app, the Docker way. ## Your development environment -Normally if you were to start writing a Python app on your laptop, your first -order of business would be to install a Python runtime onto your machine. But, -that creates a situation where the environment on your machine has to be just so -in order for your app to run as expected. +In the past, if you were to start writing a Python app, your first +order of business was to install a Python runtime onto your machine. But, +that creates a situation where the environment on your machine has to be just +so in order for your app to run as expected; ditto for the server that runs +your app. -In Docker, you can just grab an image of Python runtime that is already set up, -and use that as a base for creating your app. Then, your build can include the -base Python image right alongside your app code, ensuring that your app and the -runtime it needs to run all travel together. +With Docker, you can just grab a portable Python runtime as an image, no +installation necessary. Then, your build can include the base Python image +right alongside your app code, ensuring that your app, its dependencies, and the +runtime, all travel together. -It's done with something called a Dockerfile. +These builds are configured with something called a `Dockerfile`. ## Your first Dockerfile -Create a folder and put this file in it, with the name `Dockerfile` (no -extension). This Dockerfile defines what goes on in the environment inside your -container. Things are virtualized inside this environment, which is isolated -from the rest of your system, so you have to map ports to the outside world, and +Create an empty directory and put this file in it, with the name `Dockerfile`. +`Dockerfile` will define what goes on in the environment inside your +container. Access to resources like networking interfaces and disk drives is +virtualized inside this environment, which is isolated from the rest of your +system, so you have to map ports to the outside world, and be specific about what files you want to "copy in" to that environment. However, -after doing that, you can expect that the build of your app with this +after doing that, you can expect that the build of your app defined in this `Dockerfile` will behave exactly the same wherever it runs. {% gist johndmulhausen/c31813e076827178216b74e6a6f4a087 %} @@ -41,34 +43,30 @@ This `Dockerfile` refers to a couple of things we haven't created yet, namely `app.py` and `requirements.txt`. We'll get there. But here's what this `Dockerfile` is saying: -- Go get the base Python 2.7 runtime +- Download the official image of the Python 2.7 runtime and include it here. - Create `/app` and set it as the current working directory inside the container -- Copy the contents of my current directory (on my machine) into `/app` (in this container image) -- Install any Python packages that I list inside what is now `/app/requirements.txt` inside the container -- Ensure that this container has port 80 open when it runs +- Copy the contents of the current directory on my machine into `/app` inside the container +- Install any Python packages that I list inside `requirements.txt` +- Ensure that port 80 is exposed to the world outside this container - Set an environment variable within this container named `NAME` to be the string `World` -- Finally, when the container runs, execute `python` and pass in what is now `/app/app.py` - -This paradigm is how developing with Docker essentially works. Make a -`Dockerfile` that includes the base image, grabs your code, installs -dependencies, initializes variables, and runs the command. +- Finally, execute `python` and pass in `app.py` as the "entry point" command, + the default command that is executed at runtime. ### The app itself -Grab these two files that were referred to in the above `Dockerfile` and place -them together with `Dockerfile`, all in the same folder. +Grab these two files and place them in the same folder as `Dockerfile`. {% gist johndmulhausen/074cc7f4c26a9a8f9164b20b22602ad7 %} {% gist johndmulhausen/8728902faede400c057f3205392bb9a8 %} -You're probably getting the picture by now. In `Dockerfile` we told the `pip` -package installer to install whatever was in `requirements.txt`, which we -now see is the Flask and Redis libraries for Python. The app itself is going to -print the environment variable of `NAME`, which we set as `World`, as well as +Now we see that the `Dockerfile` command `pip install requirements.txt` installs +the Flask and Redis libraries for Python. We can also see that app itself +prints the environment variable of `NAME`, which we set as `World`, as well as the output of a call to `socket.gethostname()`, which the Docker runtime is -going to answer with the container ID. Finally, because Redis isn't running -(we've only installed the Python library), we should expect that the attempt to -use it here will fail and show the error message. +going to answer with the container ID, which is sort of like the process ID for +an executable. Finally, because Redis isn't running +(as we've only installed the Python library, and not Redis itself), we should +expect that the attempt to use it here will fail and produce the error message. ## Build the App @@ -77,8 +75,7 @@ That's it! You don't need to have installed Python or anything in your system. It doesn't seem like you've really set up an environment with Python and Flask, but you have. Let's build and run your app and prove it. -Make sure you're in the directory where you saved the three files we've shown, -and you've got everything. +7Here's what `ls` should show: ```shell $ ls @@ -86,15 +83,13 @@ Dockerfile app.py requirements.txt ``` Now run the build command. This creates a Docker image, which we're going to -tag using `-t` so it has a friendly name, which you can use interchangeable -with the image ID in commands. +tag using `-t` so it has a friendly name. ```shell -docker build -t "friendlyhello" . +docker build -t friendlyhello . ``` -In the output spew you can see everything defined in the `Dockerfile` happening, -including the installation of the packages we specified in `requirements.txt`. +In the output spew you can see everything defined in the `Dockerfile` happening. Where is your built image? It's in your machine's local Docker image registry. Check it out: @@ -106,22 +101,25 @@ friendlyhello latest 326387cea398 47 seconds ago ## Run the app -We're going to run the app and route traffic from our machine's port 80 to the -port 80 we exposed +Run the app, mapping our machine's port 4000 to the container's exposed port 80 +using `-p`: ```shell -docker run -p 80:80 friendlyhello +docker run -p 4000:80 friendlyhello ``` You should see a notice that Python is serving your app at `http://0.0.0.0:80`. -You can go there, or just to `http://localhost`, and see your app, "Hello World" -text, the container ID, and the Redis error message, all printed out in -beautiful Times New Roman. +But that message coming from inside the container, which doesn't know you +actually want to access your app at: `http://localhost:4000`. Go there, and +you'll see the "Hello World" text, the container ID, and the Redis error +message, all printed out in beautiful Times New Roman. -Hit `CTRL+C` and let's run the app in the background, in detached mode. +Hit `CTRL+C` in your terminal to quit. + +Now let's run the app in the background, in detached mode: ```shell -docker run -d -p 80:80 friendlyhello +docker run -d -p 4000:80 friendlyhello ``` You get a hash ID of the container instance and then are kicked back to your @@ -133,25 +131,22 @@ CONTAINER ID IMAGE COMMAND CREATED 1fa4ab2cf395 friendlyhello "python app.py" 28 seconds ago Up 25 seconds ``` -You'll see that `CONTAINER ID` matches what's on `http://localhost`, if you -refresh the browser page. You can't `CTRL+C` now, so let's kill the process this -way. Use the value you see under `CONTAINER ID`: +You'll see that `CONTAINER ID` matches what's on `http://localhost:4000`, if you +refresh the browser page. Now use `docker stop` to end the process, using +`CONTAINER ID`, like so: ```shell -docker kill (containerID) +docker stop 1fa4ab2cf395 ``` -## Share the App +## Share your image -Now let's test how portable this app really is. - -Sign up for Docker Hub at [https://hub.docker.com/](https://hub.docker.com/). +Sign up a Docker account at [hub.docker.com](https://hub.docker.com/). Make note of your username. We're going to use it in a couple commands. Docker Hub is a public registry. A registry is a collection of accounts and -their various repositories. A repository is a collection of assets associated -with your account - like a GitHub repository, except the code is already built. - +their various repositories. A repository is a collection of tagged images like a +GitHub repository, except the code is already built. Log in your local machine to Docker Hub. @@ -187,9 +182,40 @@ and run this command: docker run YOURUSERNAME/YOURREPO:ARBITRARYTAG ``` +> Note: If you don't specify the `:ARBITRARYTAG` portion of these commands, + the tag of `:latest` will be assumed, both when you build and when you run + images. + You'll see this stranger of a machine pull your image, along with Python and all the dependencies from `requirements.txt`, and run your code. It all travels together in a neat little package, and the new machine didn't have to install anything but Docker to run it. +## Recap and cheat sheet for images and containers + +To recap: After calling `docker run`, you created and ran a container, based on +the image created when you called `docker build`. Images are defined in a +`Dockerfile`. A container is an instance of an image, and it has any package +installations, file writes, etc that happen after you call `docker run` and run +the app. And lastly, images are shared via a registry. + +```shell +docker build -t friendlyname . #Create image using this directory's Dockerfile +docker run -p 4000:80 friendlyname #Run image "friendlyname" mapping port 4000 to 80 +docker run -d -p 4000:80 friendlyname #Same thing, but in detached mode +docker ps #See a list of all running containers +docker stop #Gracefully stop the specified container +docker ps -a #See a list of all containers on this machine, even the ones not running +docker kill #Force shutdown of the specified container +docker rm #Remove the specified container from this machine +docker rm $(docker ps -a -q) #Remove all containers from this machine +docker images -a #Show all images that have been built or downloaded onto this machine +docker rmi #Remove the specified image from this machine +docker rmi $(docker images -q) #Remove all images from this machine +docker login #Log in this CLI session using your Docker credentials (to Docker Hub by default) +docker tag username/repository:tag #Tag on your local machine for upload +docker push username/repository:tag #Upload tagged image to registry (Docker Hub by default) +docker run username/repository:tag #Run image from a registry (Docker Hub by default) +``` + [On to "Getting Started, Part 3: Stateful, Multi-container Applications" >>](part3.md){: class="button darkblue-btn"} diff --git a/getting-started/part3.md b/getting-started/part3.md index f9a51c3093..de424fe44c 100644 --- a/getting-started/part3.md +++ b/getting-started/part3.md @@ -9,8 +9,8 @@ wrote, built, ran, and shared our first Dockerized app, which all fit in a single container. In part 3, we will expand this application so that it is comprised of two -containers simultaneously: one running the web app we have already written, and -another that stores data on the web app's behalf. +containers running simultaneously: one running the web app we have already +written, and another that stores data on the web app's behalf. ## Understanding services @@ -28,24 +28,23 @@ and that's going to happen via a different executable entirely. In a distributed application, these different pieces of the app are called "services." For example, if you imagine a video sharing site, there will -probably be a service for storing application data in a database, another one +probably be a service for storing application data in a database, a service for video transcoding in the background after a user uploads something, a -service for streaming, and so on, and they all need to work in concert. +service for the front-end, and so on, and they all need to work in concert. -The easiest way to introduce the organization of your app into services using -containers is by using Docker Compose. We're going to add a data storage service +The easiest way to organize your containerized app into services using +is using Docker Compose. We're going to add a data storage service to our simple Hello World app. Don't worry, it's shockingly easy. ## Your first `docker-compose.yml` File -As you saw, a `Dockerfile` is a text file that defines a single Docker image. -But a `docker-compose.yml` file is a YAML markup file that is hierarchical in +A `docker-compose.yml` file is a YAML markup file that is hierarchical in structure, and defines how multiple Docker images should work together when they are running in containers. We saw that the "Hello World" app we created looked for a running instance of Redis, and if it failed, it produced an error message. All we need is a running -Redis instance, and that error message will be replaced with a visitor counter. +Redis instance, and that error message will be replaced with a visitor counter. Well, just as we grabbed the base image of Python earlier, we can grab the official image of Redis, and run that right alongside our app. @@ -63,14 +62,14 @@ image](https://store.docker.com/images/1f6ef28b-3e48-4da1-b838-5bd8710a2053)). This `docker-compose.yml` file tells Docker to do the following: -- Pull and run [the image we uploaded in step 2](/getting-started/part2/#/share-the-app) as a service called `web` -- Map port 80 on the host to the container's port 80 (so http://localhost:80 resolves properly) -- Link this container to the service we named `redis`; this ensures that the - dependency between `redis` and `web` is expressed, as well as the order of service - startup. -- Pull and run the official Redis image as a service called `redis` +- Pull and run [the image we uploaded to Docker Hub in step 2](/getting-started/part2/#/share-the-app) as a service called `web` +- Map port 4000 on the host to `web`'s port 80 +- Link the `web` service to the service we named `redis`; this ensures that the + dependency between `redis` and `web` is expressed, and these containers will + run together in the same subnet. +- Our service named `redis` just runs the official Redis image, so go get it from Docker Hub. -## Run your first multi-container app +## Run and scale up your first multi-container app Run this command in the directory where you saved `docker-compose.yml`: @@ -79,52 +78,63 @@ docker-compose up ``` This will pull all the necessary images and run them in concert. Now when you -visit `http://localhost`, you'll see a number next to the visitor counter +visit `http://localhost:4000`, you'll see a number next to the visitor counter instead of the error message. It really works -- just keep hitting refresh. -## Connecting to this instance of Redis +## Connecting to containers with port mapping With a containerized instance of Redis running, you're probably wondering -- how do I break through the wall of isolation and manage my data? The answer is, port mapping. [The page for the official Redis image](https://store.docker.com/images/1f6ef28b-3e48-4da1-b838-5bd8710a2053) -states that the normal management ports are open in their image, so you should +states that the normal management ports are open in their image, so you would be able to connect to it at `localhost:6379` if you add a `ports:` section to `docker-compose.yml` under `redis` that maps `6379` to your host, just as port -`80` is mapped for `web`. Same with MySQL or any other data solution; -containerized doesn't mean unreachable, it just means portable. Once you map -your ports, you can use your fave UI tools like MySQL Workbench, Redis Desktop -Manager, etc, to connect to your Dockerized instance. +`80` is mapped for `web`. Same with MySQL or any other data solution; once you +map your ports, you can use your fave UI tools like MySQL Workbench, Redis +Desktop Manager, etc, to connect to your Dockerized instance. Redis port mapping isn't necessary in `docker-compose.yml` because the two -services (`web` and `redis`) are linked, ensuring they run on the same host (VM -or physical machine). Within that host, the containers can talk to each other -in a private subnet that is automatically created by the Docker runtime, which -isn't accessible to the outside world, only to other containers. In our app, -we specify `EXPOSE 80` in our Dockerfile, and as you can see in the Redis -documentation, they specify `EXPOSE 6379` in the Dockerfile that defines the -official Redis image. But those ports aren't accessible outside of the private -subnet (or, in turn, reachable at `http://localhost`) until you map the host's -port 80 to the container's port 80, which is why we specified as much in -`docker run` previously, and `docker-compose.yml` just now. +services (`web` and `redis`) are linked, ensuring they run on the same host (VM +or physical machine), in a private subnet that is automatically created by the +Docker runtime. Containers within +that subnet can already talk to each other; it's connecting from the outside +that necessitates port mapping. + +## Cheat sheet and recap: Hosts, subnets, and Docker Compose + +You learned that by creating a `docker-compose.yml` file, you can define the +entire stack for your application. This ensures that your services run +together in a private subnet that lets them connect to each +other, but only to the world as specifically dircted. This means that if you +want to connect your favorite data management software to your data storage +service, you'll have to ensure the container has the proper port exposed and +your host has that port mapped to the container in `docker-compose.yml`. + +```shell +docker-compose up #Pull and run images specified in `docker-compose.yml` as services +docker-compose up -d #Same thing, but in background mode +docker-compose stop #Stop all running containers for this app +docker-compose rm -f #Remove all containers for this app +``` ## Get ready to scale Until now, I've been able to shield you from worrying too much about host management. That's because installing Docker always sets up a default way -to run containers in a single-host environment. Docker for Windows and Mac +to run containers on that machine. Docker for Windows and Mac comes with a virtual machine host running a lighweight operating system -we call Moby, which is just a very slimmed-down Linux. Docker for Linux -just works without a VM at all. And Docker for Windows can run Microsoft +we call Moby, which is just a very slimmed-down Linux. Docker for Linux +just works without a VM at all. And Docker for Windows can even run Microsoft Windows containers using native Hyper-V support. When you've run `docker -run` and `docker-compose up` so far, Docker has used these default hosts +run` and `docker-compose up` so far, Docker has used these solutions to run your containers. That's because we want you to be able to install Docker and get straight to the work of development and building images. But when it comes to getting your app into production, we all know that you're not going to run just one host machine that has Redis, Python, and all your other sevices. That won't scale. You need to learn how to run not -just multiple containers on one host, but multiple containers on multiple -hosts. And that's precisely what we're going to get into next. +just multiple containers on your local host, but multiple containers on +multiple hosts. And that's precisely what we're going to get into next. [On to "Part 4: Running our App in Production" >>](part4.md){: class="button darkblue-btn"} diff --git a/getting-started/part4.md b/getting-started/part4.md index 465a7adf8f..43dc759a46 100644 --- a/getting-started/part4.md +++ b/getting-started/part4.md @@ -98,15 +98,18 @@ into a swarm. ## Creating your first Swarm cluster 1. Go back to Docker Cloud by visiting [cloud.docker.com](https://cloud.docker.com). -2. Click **Node Clusters** in the left-navigation, then click the **Create** button. +2. Click **Node Clusters** in the left navigation, then click the **Create** button. This pulls up a form where you can create our cluster. 3. Leave everything default, except: - - Name: Give your cluster a name. - - Region: Select a region that's close to you. + - Name: Give your cluster a name + - Region: Select a region that's close to you - Provider: Set to "Amazon Web Services" - - Type/Size: -4. Launch the cluster by clicking **Launch node cluster**, and -5. + - Type/Size: Select the `t2.nano` option as that is free-tier +4. Launch the cluster by clicking **Launch node cluster**; this will spin + up a free-tier Amazon instance. +5. Now, click **Services** in the left navigation, then the **Create** button, + then the **globe icon**. +6. Search Docker Hub for the image you uploaded [On to next >>](part5.md){: class="button darkblue-btn"} diff --git a/js/menu.js b/js/menu.js index 0f875cdb05..b2e186df72 100644 --- a/js/menu.js +++ b/js/menu.js @@ -54,8 +54,8 @@ jQuery(document).ready(function(){ var index = 0; var currentHeader = 0, lastHeader = 0; - var output = ""; - $("h2, h3, h4").each(function() { + var output = "
    "; + $("h1, h2, h3, h4").each(function() { var li= "
  • " + $(this).text().replace("ΒΆ","") + "
  • "; if( $(this).is("h2") ){ // h2 @@ -67,12 +67,14 @@ jQuery(document).ready(function(){ // h4 currentHeader = 4; } + console.log("currentHeader ",currentHeader, "lastHeader ",lastHeader, "text ", $(this).text()); if (currentHeader > lastHeader) { // nest further output += "
      " } if (currentHeader < lastHeader && lastHeader > 0) { // close nesting + console.log("Closing nesting because ", lastHeader, "is <", currentHeader); for (i=0; i < (lastHeader - currentHeader); i++) { output += "
    "