使用 Ansible Container 构建和测试应用程序
Ansible 容器
![](/data/attachment/album/201711/19/061202bud9plxpcqeeztlz.jpg)


容器是一个日益流行的开发环境。作为一名开发人员，你可以选择多种工具来管理你的容器。本文将向你介绍 Ansible Container，并展示如何在类似生产环境中运行和测试你的应用程序。


### 入门


这个例子使用了一个简单的 Flask Hello World 程序。这个程序就像在生产环境中一样由 Apache HTTP 服务器提供服务。首先，安装必要的 `docker` 包：



```
sudo dnf install docker

```

Ansible Container 需要通过本地套接字与 Docker 服务进行通信。以下命令将更改套接字所有者，并将你添加到可访问此套接字的 `docker` 用户组：



```
sudo groupadd docker && sudo gpasswd -a $USER docker
MYGRP=$(id -g) ; newgrp docker ; newgrp $MYGRP

```

运行 `id` 命令以确保 `docker` 组在你的组成员中列出。最后，[使用 sudo](https://fedoramagazine.org/howto-use-sudo/) 启用并启动 docker 服务：



```
sudo systemctl enable docker.service
sudo systemctl start docker.service

```

### 设置 Ansible Container


Ansible Container 使你能够构建容器镜像并使用 Ansible playbook 进行编排。该程序在一个 YAML 文件中描述，而不是使用 Dockerfile，列出组成容器镜像的 Ansible 角色。


不幸的是，Ansible Container 在 Fedora 中没有 RPM 包可用。要安装它，请使用 python3 虚拟环境模块。



```
mkdir ansible-container-flask-example
cd ansible-container-flask-example
python3 -m venv .venv
source .venv/bin/activate
pip install ansible-container[docker]

```

这些命令将安装 Ansible Container 及 Docker 引擎。 Ansible Container 提供三种引擎：Docker、Kubernetes 和 Openshift。


### 设置项目


现在已经安装了 Ansible Container，接着设置这个项目。Ansible Container 提供了一个简单的命令来创建启动所需的所有文件：



```
ansible-container init

```

来看看这个命令在当前目录中创建的文件：


* `ansible.cfg`
* `ansible-requirements.txt`
* `container.yml`
* `meta.yml`
* `requirements.yml`


该项目仅使用 `container.yml` 来描述程序服务。有关其他文件的更多信息，请查看 Ansible Container 的[入门](https://docs.ansible.com/ansible-container/getting_started.html)文档。


### 定义容器


如下更新 `container.yml`：



```
version: "2"
settings:
  conductor:
    # The Conductor container does the heavy lifting, and provides a portable
    # Python runtime for building your target containers. It should be derived
    # from the same distribution as you're building your target containers with.
    base: fedora:26
    # roles_path:   # Specify a local path containing Ansible roles
    # volumes:      # Provide a list of volumes to mount
    # environment:  # List or mapping of environment variables

  # Set the name of the project. Defaults to basename of the project directory.
  # For built services, concatenated with service name to form the built image name.
  project_name: flask-helloworld

services: 
  # Add your containers here, specifying the base image you want to build from.
  # To use this example, uncomment it and delete the curly braces after services key.
  # You may need to run `docker pull ubuntu:trusty` for this to work.
  web:
    from: "fedora:26"
    roles: 
      - base
    ports:
      - "5000:80"
    command: ["/usr/bin/dumb-init", "httpd", "-DFOREGROUND"]
    volumes:
      - $PWD/flask-helloworld:/flaskapp:Z

```

`conductor` 部分更新了基本设置以使用 Fedora 26 容器基础镜像。


`services` 部分添加了 `web` 服务。这个服务使用 Fedora 26，后面有一个名为 `base` 的角色。它还设置容器和主机之间的端口映射。Apache HTTP 服务器为容器的端口 80 上的 Flask 程序提供服务，该容器重定向到主机的端口 5000。然后这个文件定义了一个卷，它将 Flask 程序源代码挂载到容器中的 `/flaskapp` 中。


最后，容器启动时运行 `command` 配置。这个例子中使用 [dumb-init](https://github.com/Yelp/dumb-init)，一个简单的进程管理器并初始化系统启动 Apache HTTP 服务器。


### Ansible 角色


现在已经设置完了容器，创建一个 Ansible 角色来安装并配置 Flask 程序所需的依赖关系。首先，创建 `base` 角色。



```
mkdir -p roles/base/tasks
touch roles/base/tasks/main.yml

```

现在编辑 `main.yml` ，它看起来像这样：



```
---
- name: Install dependencies 
  dnf: pkg={{item}} state=present
  with_items:
    - python3-flask
    - dumb-init
    - httpd
    - python3-mod_wsgi

- name: copy the apache configuration
  copy:
    src: flask-helloworld.conf
    dest: /etc/httpd/conf.d/flask-helloworld.conf
    owner: apache
    group: root
    mode: 655

```

这个 Ansible 角色是简单的。首先它安装依赖关系。然后，复制 Apache HTTP 服务器配置。如果你对 Ansible 角色不熟悉，请查看[角色文档](http://docs.ansible.com/ansible/latest/playbooks_reuse_roles.html)。


### Apache HTTP 配置


接下来，通过创建 `flask-helloworld.conf` 来配置 Apache HTTP 服务器：



```
$ mkdir -p roles/base/files
$ touch roles/base/files/flask-helloworld.conf

```

最后将以下内容添加到文件中：



```
<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess hello_world user=apache group=root
    WSGIScriptAlias / /flaskapp/flask-helloworld.wsgi

    <Directory /flaskapp>
        WSGIProcessGroup hello_world
        WSGIApplicationGroup %{GLOBAL}
    Require all granted
    </Directory>
</VirtualHost>

```

这个文件的重要部分是 `WSGIScriptAlias`。该指令将脚本 `flask-helloworld.wsgi` 映射到 `/`。有关 Apache HTTP 服务器和 mod\_wsgi 的更多详细信息，请阅读 [Flask 文档](http://flask.pocoo.org/docs/0.12/deploying/mod_wsgi/)。


### Flask “hello world”


最后，创建一个简单的 Flask 程序和 `flask-helloworld.wsgi` 脚本。



```
mkdir flask-helloworld
touch flask-helloworld/app.py
touch flask-helloworld/flask-helloworld.wsgi

```

将以下内容添加到 `app.py`：



```
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

```

然后编辑 `flask-helloworld.wsgi` ，添加这个：



```
import sys
sys.path.insert(0, '/flaskapp/')

from app import app as application

```

### 构建并运行


现在是时候使用 `ansible-container build` 和 `ansible-container run` 命令来构建和运行容器。



```
ansible-container build

```

这个命令需要一些时间来完成，所以要耐心等待。



```
ansible-container run

```

你现在可以通过以下 URL 访问你的 flask 程序： http://localhost:5000/


### 结论


你现在已经看到如何使用 Ansible Container 来管理、构建和配置在容器中运行的程序。本例的所有配置文件和源代码在 [Pagure.io](https://pagure.io/ansible-container-flask-example) 上。你可以使用此例作为基础来开始在项目中使用 Ansible Container。




---


via: <https://fedoramagazine.org/build-test-applications-ansible-container/>


作者：[Clement Verna](https://fedoramagazine.org/author/cverna/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Containers are an increasingly popular development environment. As a developer you have a large choice of tools to manage your containers. This article introduces you to Ansible Container and shows how you can run and test your application in a production-like environment.


## Getting started

This example uses a simple Flask Hello World application. This application is served by an Apache HTTP server just like in production. First, install the requisite *docker* package:

sudo dnf install docker

Ansible Container needs to communicate with the docker service through its local socket. The following commands change the socket ownership, and add you to a *docker* group that can access the socket:

sudo groupadd docker && sudo gpasswd -a $USER docker MYGRP=$(id -g) ; newgrp docker ; newgrp $MYGRP

Run the *id* command to ensure the *docker* group is listed in your group memberships. Finally, enable and start the docker service [using sudo](https://fedoramagazine.org/howto-use-sudo/):

sudo systemctl enable docker.service sudo systemctl start docker.service

## Setting up Ansible Container

Ansible Container enables you to build container images and orchestrate them using only Ansible playbooks. The application is described in a single YAML file, and instead of using a Dockerfile, lists Ansible roles that make up the container images.

Unfortunately Ansible Container is not yet available as an RPM package in Fedora. To install it, use the python3 virtual environment module.

mkdir ansible-container-flask-example cd ansible-container-flask-example python3 -m venv .venv source .venv/bin/activate pip install ansible-container[docker]

These commands install Ansible Container with the Docker engine. Ansible Container provides three engines: Docker, Kubernetes and Openshift.

## Setting up the project

Now that Ansible Container is installed, set up the project. Ansible Container provides a simple command to create all files needed to get started:

ansible-container init

Now look at the files this command created in the current directory:

*ansible.cfg**ansible-requirements.txt**container.yml**meta.yml**requirements.yml*

This project uses only the *container.yml* file to describe the application services. For more information about the other files, check out the [Getting Started](https://docs.ansible.com/ansible-container/getting_started.html) documentation of Ansible Container.

## Defining the container

Update *container.yml* as follows:

version: "2" settings: conductor: # The Conductor container does the heavy lifting, and provides a portable # Python runtime for building your target containers. It should be derived # from the same distribution as you're building your target containers with. base: fedora:26 # roles_path: # Specify a local path containing Ansible roles # volumes: # Provide a list of volumes to mount # environment: # List or mapping of environment variables # Set the name of the project. Defaults to basename of the project directory. # For built services, concatenated with service name to form the built image name. project_name: flask-helloworld services: # Add your containers here, specifying the base image you want to build from. # To use this example, uncomment it and delete the curly braces after services key. # You may need to run `docker pull ubuntu:trusty` for this to work. web: from: "fedora:26" roles: - base ports: - "5000:80" command: ["/usr/bin/dumb-init", "httpd", "-DFOREGROUND"] volumes: - $PWD/flask-helloworld:/flaskapp:Z

The *conductor* section updates the base setting to use a Fedora 26 container base image.

The *services* section adds the *web* service. This service uses Fedora 26 and has a *role* called *base* to be defined later. It also sets up the port mapping between the container and host. The Apache HTTP server serves the Flask application on port 80 of the container, which redirects to port 5000 of the host. Then this file defines a *volume* that mounts the Flask application source code to */flaskapp* in the container.

Finally the *command* configuration runs when the container starts. This example uses [dumb-init](https://github.com/Yelp/dumb-init), a simple process supervisor and init system to start the Apache HTTP server.

## Ansible role

Now that the container is setup, create an Ansible role to install and configure the dependencies needed by the Flask application. First, create the *base* role.

mkdir -p roles/base/tasks touch roles/base/tasks/main.yml

Now edit the *main.yml* file so that it looks like this:

--- - name: Install dependencies dnf: pkg={{item}} state=present with_items: - python3-flask - dumb-init - httpd - python3-mod_wsgi - name: copy the apache configuration copy: src: flask-helloworld.conf dest: /etc/httpd/conf.d/flask-helloworld.conf owner: apache group: root mode: 655

This Ansible role is a simple one. First it installs dependencies. Then, it copies the Apache HTTP server configuration. If you’re not familiar with Ansible roles, check out the [Roles documentation](http://docs.ansible.com/ansible/latest/playbooks_reuse_roles.html).

## Apache HTTP configuration

Next, configure the Apache HTTP server by creating the *flask-helloworld.conf* file:

$ mkdir -p roles/base/files $ touch roles/base/files/flask-helloworld.conf

And finally add the following to the file:

<VirtualHost *> ServerName example.com WSGIDaemonProcess hello_world user=apache group=root WSGIScriptAlias / /flaskapp/flask-helloworld.wsgi <Directory /flaskapp> WSGIProcessGroup hello_world WSGIApplicationGroup %{GLOBAL} Require all granted </Directory> </VirtualHost>

The important part of this file is the *WSGIScriptAlias.* This instruction maps the script *flask-helloworld.wsgi* to the “/” URL. For more details on Apache HTTP server and mod_wsgi, read [the Flask documentation](http://flask.pocoo.org/docs/0.12/deploying/mod_wsgi/).

## Flask “hello world”

Finally, create a simple Flask application and the* flask-helloworld.wsgi* script.

mkdir flask-helloworld touch flask-helloworld/app.py touch flask-helloworld/flask-helloworld.wsgi

Add the following to *app.py***:**

from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!"

Then edit *flask-helloworld.wsgi* to add this:

import sys sys.path.insert(0, '/flaskapp/') from app import app as application

## Build and run

Now it’s time to build and run the container with the *ansible-container build* and *ansible-container run* commands.

ansible-container build

This command takes a bit of time to complete, so be patient.

ansible-container run

You can now access your flask application at this URL: *http://localhost:5000/*

## Conclusion

You’ve now seen how to use Ansible Container to manage, build and configure your applications running inside a container. All the configuration files and the source code of this example are hosted on [Pagure.io](https://pagure.io/ansible-container-flask-example). You can use this example as the base to start using Ansible Container on your projects.

## Andre

Hi Clement, thanks for this post.

Note:

At the apache http configuration part in the text part you talk about flask-helloworld.conf file but in the the screen below that you state “touch ansible-helloworld.conf”.

best regards Andre

## Paul W. Frields

@Andre: Thanks for catching this error, which is now fixed.

## eeee

is possible create a docker image for package maker?

easy way to create my own package with gcc, make, etc.

## Paul W. Frields

Certainly. You could simply change the install dependencies in the ansible configuration, where in this example it installs the web server, Flask, and WSGI bits.

## Adam Young

Seems to e something missing in the error handling. My notoriously sloppy copy from the article kept leading to stack traces(my fault) but sometimes the traces themselves showed additional problems. FOr example, first go round, I didn’t remove a stray{} from container.yaml and got an excetion, which triggered a second exception:

During handling of the above exception, another exception occurred:

…

docker/config.py”, line 28, in set_env

raise AnsibleContainerConfigException(u”Parsing container.yml – %s” % unicode(exc))

NameError: name ‘unicode’ is not defined

which looks like a missing dependency. or something not doing python3 right? This is in a venv, so it should all be python3. Anyway, I was able to fix the original error and moved on to:

ERROR Error applying role! engine=<container.docker.engine.Engine object at 0x7f60fc5f2050> exit_code=2 playbook=[{‘hosts’: u’web’, ‘roles’: [‘base’], ‘vars’: {}}]

Traceback (most recent call last):

…

File “/_ansible/container/core.py”, line 813, in conductorcmd_build

raise RuntimeError(‘Build failed.’)

RuntimeError: Build failed.

I’m sure I have a syntax problem somewhere, but this is a little hard to debug.

ERROR Conductor exited with status 1

## Adam Young

Should have added this bit:

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: AttributeError: ‘module’ object has no attribute ‘exceptions’

failed: [web] (item=[u’python3-flask’, u’dumb-init’, u’httpd’, u’python3-mod_wsgi’]) => {“changed”: false, “failed”: true, “item”: [“python3-flask”, “dumb-init”, “httpd”, “python3-mod_wsgi”], “module_stderr”: “Traceback (most recent call last):\n File \”/tmp/ansible_BendGt/ansible_module_dnf.py\”, line 534, in \n main()\n File \”/tmp/ansible_BendGt/ansible_module_dnf.py\”, line 501, in main\n _ensure_dnf(module)\n File \”/tmp/ansible_BendGt/ansible_module_dnf.py\”, line 190, in _ensure_dnf\n import dnf\n File \”/_usr/lib/python2.7/site-packages/dnf/

init.py\”, line 26, in \n warnings.filterwarnings(‘once’, category=dnf.exceptions.DeprecationWarning)\nAttributeError: ‘module’ object has no attribute ‘exceptions’\n”, “module_stdout”: “”, “msg”: “MODULE FAILURE”, “rc”: 0}## Clement Verna

@Adam, I do agree that error handling in Ansible Container could be better. Did you manage to solve the last exception ?

## Mark Lamourine

The one thing I’d add is actually about the setup. When you add users to the docker group, they become effectively root. Like anything else it’s fine if that’s what you meant to do, but you need to protect access to that user in the same way you’d protect access to root.

I set sudo for my personal account on my laptop so adding that account to the docker group is no big deal. Adding a non-priv account on a server to the docker group is a new potential attack vector that could be easily overlooked.

The rest looks great and I’m going to go try it now.

Mark

## Benjamin

worked like a charm, thanks for putting this together!

## Jan

Setting up the virtual environment fails in Fedora 27. Working around by manually installing pip:

cd ansible-container-flask-example

python3 -m venv .venv –without-pip

source .venv/bin/activate

wget https://bootstrap.pypa.io/get-pip.py

python get-pip.py

pip install ansible-container[docker]

Everything else works fine.

## Paul W. Frields

@Jan: interesting, these instructions worked fine for me on a standard F27 installation. Have you tried in a fresh user account to see if the issue is some conflict with your personal configuration?

## Brian Clark

I need a second set of eyes on this: When trying to run the example during the ‘build’ command I get a traceback saying something about a socket timeout. I tried to figure this out I think it is a docker error or user error but not sure what. I ran the build command with –debug to see if it would give me a clue.

2017-11-12T16:39:30.713079 Docker run: [container.docker.engine] caller_file=/home/bclark/myapp/ansible-container-flask-example/.venv/lib64/python3.6/site-packages/container/docker/engine.py caller_func=run_conductor caller_line=435 image=sha256:ac3ab4febc9ee3ef99a3abcebb822fcd6c0b4f54a92895c89a6926dc7faf22d6 params={‘name’: ‘flask-helloworld_conductor’, ‘command’: [‘conductor’, ‘build’, ‘–project-name’, ‘flask-helloworld’, ‘–engine’, ‘docker’, ‘–params’, ‘eyJkZWJ1ZyI6IHRydWUsICJkZXZlbCI6IGZhbHNlLCAic2VsaW51eCI6IHRydWUsICJzdWJjb21tYW5kIjogImJ1aWxkIiwgImZsYXR0ZW4iOiBmYWxzZSwgInB1cmdlX2xhc3QiOiB0cnVlLCAic2F2ZV9jb25kdWN0b3JfY29udGFpbmVyIjogZmFsc2UsICJzZXJ2aWNlc190b19idWlsZCI6IG51bGwsICJjYWNoZSI6IHRydWUsICJjb25kdWN0b3JfY2FjaGUiOiB0cnVlLCAiY29udGFpbmVyX2NhY2hlIjogdHJ1ZSwgImxvY2FsX3B5dGhvbiI6IGZhbHNlLCAic3JjX21vdW50X3BhdGgiOiBudWxsLCAiYW5zaWJsZV9vcHRpb25zIjogIiIsICJyb2xlc19wYXRoIjogW10sICJ3aXRoX3ZvbHVtZXMiOiBbXSwgInZvbHVtZV9kcml2ZXIiOiBudWxsLCAid2l0aF92YXJpYWJsZXMiOiBbXSwgImNvbmZpZ192YXJzIjoge319’, ‘–config’, ‘eyJ2ZXJzaW9uIjogIjIiLCAic2V0dGluZ3MiOiBbWyJjb25kdWN0b3IiLCB7ImJhc2UiOiAiZmVkb3JhOjI2In1dLCBbInByb2plY3RfbmFtZSIsICJmbGFzay1oZWxsb3dvcmxkIl0sIFsicHdkIiwgIi9ob21lL2JjbGFyay9teWFwcC9hbnNpYmxlLWNvbnRhaW5lci1mbGFzay1leGFtcGxlIl1dLCAic2VydmljZXMiOiBbWyJ3ZWIiLCB7ImZyb20iOiAiZmVkb3JhOjI2IiwgInJvbGVzIjogWyJiYXNlIl0sICJwb3J0cyI6IFsiNTAwMDo4MCJdLCAiY29tbWFuZCI6IFsiL3Vzci9iaW4vZHVtYi1pbml0IiwgImh0dHBkIiwgIi1ERk9SRUdST1VORCJdLCAidm9sdW1lcyI6IFsiL2hvbWUvYmNsYXJrL215YXBwL2Fuc2libGUtY29udGFpbmVyLWZsYXNrLWV4YW1wbGUvZmxhc2staGVsbG93b3JsZDovZmxhc2thcHA6WiJdfV1dLCAiZGVmYXVsdHMiOiBbXX0=’, ‘–encoding’, ‘b64json’], ‘detach’: True, ‘user’: ‘root’, ‘volumes’: {‘flask-helloworld_secrets’: {‘bind’: ‘/run/secrets’, ‘mode’: ‘rw’}, ‘/home/bclark/myapp/ansible-container-flask-example’: {‘bind’: ‘/src’, ‘mode’: ‘ro’}, ‘/var/run/docker.sock’: {‘bind’: ‘/var/run/docker.sock’, ‘mode’: ‘rw’}, ‘/home/bclark/.docker/config.json’: {‘bind’: ‘/home/bclark/.docker/config.json’, ‘mode’: ‘rw’}}, ‘environment’: {‘DOCKER_HOST’: ‘unix:///var/run/docker.sock’, ‘ANSIBLE_ROLES_PATH’: ‘/src/roles:/etc/ansible/roles’}, ‘working_dir’: ‘/src’, ‘cap_add’: [‘SYS_ADMIN’], ‘privileged’: True}

2017-11-12T16:40:30.799135 Unknown exception UnixHTTPConnectionPool(host=’localhost’, port=None): Read timed out. (read timeout=60) [container.cli] caller_file=/home/bclark/myapp/ansible-container-flask-example/.venv/lib64/python3.6/site-packages/structlog/stdlib.py caller_func=exception caller_line=95

## Clement Verna

Hi Brian, I did a little bit of DuckDuckGoing and found a similar thread on Docker Compose (https://github.com/docker/compose/issues/1045)

A possible work around seems to set a longer timeout like so :

export DOCKER_CLIENT_TIMEOUT=120

Hope it helps.

## Brian Clark

Thanks for the help. It looks like I might not have enough resources. So I tried running with no X got an Internal error but did not see if it happen before the time out or after. Again thanks for the help.

## Paul W. Frields

@Brian: You may want to check a couple things: (1) Is the docker service started? (

) (2) Is /var/run/docker.sock permissions 660, owned by root/docker? (3) Is your user in the docker group? (

)