Swarmlab docs

Application development in a distributed system

Development of Distributed Systems from Design to Application


You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

413 lines
9.6 KiB

3 years ago
= Docker build!
.Dockerfile instructions
****
Docker builds images automatically by reading the instructions from a Dockerfile _-- a text file that contains all commands, in order, needed to build a given image._
A Docker image consists of read-only layers each of which represents a Dockerfile instruction.
The layers are stacked and each one is a delta of the changes from the previous layer.
Each instruction creates one layer:
* FROM creates a layer from the ubuntu:18.04 Docker image
* COPY adds files from your Docker client’s current directory.
* RUN builds your application with make.
* CMD specifies what command to run within the container.
When you run an image and generate a container, you add a new writable layer (the “container layer”) on top of the underlying layers.
All changes made to the running container, such as writing new files, modifying existing files, and deleting files, are written to this thin writable container layer.
****
== Sample Application
Let's starts with a simple Node.js application
=== Dockerfile
.Dockerfile
[source,yaml]
----
3 years ago
FROM node:14-alpine
3 years ago
WORKDIR /code
COPY package.json /code/package.json // <1>
RUN npm install \ // <2>
&& npm install -g nodemon@1.11.0 \ // <3>
&& npm cache clean --force; //<4>
COPY app.js /code // <5>
COPY index.html /code // <5>
CMD ["npm", "start"] // <6>
----
<1> Copy package.json file
<2> Install the dependencies in the local node_modules folder.
<3> Install global
<4> Clean cache
<5> Copy NodeJS application
<6> Run application
=== Dockerfile instructions
==== FROM
The *FROM instruction* initializes a new build stage and sets the Base Image for subsequent instructions.
As such, a valid Dockerfile must start with a FROM instruction. The image can be any valid image
TIP: We recommend the Alpine image as it is tightly controlled and small in size (currently under 5 MB), while still being a full Linux distribution.
==== LABEL
The LABEL instruction adds metadata to an image. A LABEL is a key-value pair. To include spaces within a LABEL value, use quotes and backslashes as you would in command-line parsing.
A example:
[source,dockerfile]
----
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
----
==== RUN
RUN has 2 forms:
* RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
* RUN ["executable", "param1", "param2"] (exec form)
The *RUN instruction* will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.
[NOTE]
====
Split long or complex RUN statements on multiple lines separated with backslashes to make your Dockerfile more readable, understandable, and maintainable.
For example:
[source,dockerfile]
----
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo \
&& rm -rf /var/lib/apt/lists/*
----
====
==== CMD
The main purpose of a CMD is to provide defaults for an executing container.
The CMD instruction has three forms:
* CMD ["executable","param1","param2"] (exec form, this is the preferred form)
* CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
* CMD command param1 param2 (shell form)
NOTE: There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.
==== EXPOSE
The *EXPOSE instruction* indicates the ports on which a container listens for connections.
[source,dockerfile]
----
EXPOSE 8000
----
NOTE: The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.
==== ENV
To make new software easier to run, you can use ENV
For example
[source,dockerfile]
----
ENV PATH=/usr/local/nginx/bin:$PATH
----
ensures that CMD ["nginx"] just works.
[source,dockerfile]
----
ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH
----
==== ADD or COPY
[NOTE]
====
Although ADD and COPY are functionally similar.
* COPY copies a file/directory from your host to your image.
* ADD copies a file/directory from your host to your image, but can also fetch remote URLs, extract TAR files, etc...
* *Use COPY* for simply copying files and/or directories into the build context.
* *Use ADD* for downloading remote resources, extracting TAR files, etc..
====
===== COPY
COPY has two forms:
[source,dockerfile]
----
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
e.g.
COPY home /mydir/
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
----
The *COPY instruction* copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>.
===== ADD
ADD has two forms:
[source,dockerfile]
----
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
e.g.
ADD home /mydir/
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
----
The *ADD instruction* copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>
==== ENTRYPOINT
ENTRYPOINT has two forms:
The exec form, which is the preferred form:
[source,dockerfile]
----
ENTRYPOINT ["executable", "param1", "param2"]
----
The shell form:
[source,dockerfile]
----
ENTRYPOINT command param1 param2
----
An ENTRYPOINT allows you to configure a container that will run as an executable.
[source,dockerfile]
----
docker run -i -t --rm -p 80:80 nginx
----
Let’s see with an example of an image for the command line tool s3cmd:
[source,dockerfile]
----
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
----
Now the image can be run like this to show the command’s help:
[source,dockerfile]
----
docker run s3cmd
----
==== USER
The *USER instruction* sets the user name (or UID) and optionally the user group (or GID) to use when running the image and for any RUN, CMD and ENTRYPOINT instructions that follow it in the Dockerfile.
If a service can run without privileges, use USER to change to a non-root user.
[source,dockerfile]
----
FROM alpine
USER swarmlab
----
==== WORKDIR
The *WORKDIR instruction* sets the working directory for any *RUN, CMD, ENTRYPOINT, COPY and ADD instructions* that follow it in the Dockerfile.
If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction.
NOTE: For clarity and reliability, you should always use absolute paths for your WORKDIR
=== package.json
.package.json
[source,javascript]
----
{
"main": "app.js",
"dependencies": {
"express": "~4.14.0",
"express-handlebars": "~3.0.0"
}
}
----
[NOTE]
====
A package.json file:
* lists the packages your project depends on
* specifies versions of a package that your project can use using semantic versioning rules
* makes your build reproducible, and therefore easier to share with other developers
====
=== app.js
.app.js
[source,javascript]
----
var express = require('express');
var expressHandlebars = require('express-handlebars');
var http = require('http');
var PORT = 8000;
var LINES = [
"Ποιος μας γηροκομεί τη σήμερον ημέρα, ψηστιέρα, καρβουνιέρα μούσα δεκεμβριανή.",
"Πολέμησα καιρό σε όλα τα πεδία και με τυφλή μανία ξέσκιζα τον εχθρό.",
"Τώρα με χειρουργεί η αλλήθωρη νεολαία, μια τσογλανοπαρέα, που κάνει κριτική.",
];
var lineIndex = 0;
var app = express();
app.engine('html', expressHandlebars());
app.set('view engine', 'html');
app.set('views', __dirname);
app.get('/', function(req, res) {
var message = LINES[lineIndex];
lineIndex += 1;
if (lineIndex >= LINES.length) {
lineIndex = 0;
}
res.render('index', {message: message});
});
http.Server(app).listen(PORT, function() {
console.log("HTTP server listening on port %s", PORT);
});
----
=== index.html
.index.html
[source,javascript]
----
<html>
<head>
<meta http-equiv="refresh" content="2">
<style type="text/css">
body {
font-family: Helvetica, Arial, sans-serif;
font-weight: 600;
font-size: 56pt;
text-transform: uppercase;
text-align: center;
background: #3c3;
color: white;
}
</style>
</head>
<body>&ldquo;{{message}}&rdquo;</body>
</html>
----
== Docker build
Build an image from a Dockerfile
[source,javascript]
----
docker build [OPTIONS] PATH | URL | -
----
Create files
* Dockerfile
* app.js
* package.json
* index.html
.docker build
[source,javascript]
----
docker build -f Dockerfile -t mynodejs .
----
== Start
Now that we have an image, let’s run the application! To do so, we will use the docker run command
[source,javascript]
----
docker run -it -p 8000:8000 mynodejs /bin/sh -c "node app.js"
----
TIP: Ctrl + C tells the program that you want to interrupt
[source,javascript]
----
http://localhost:8000
----
*RUN* URL
in a web browser