Containerizing Apache, MySQL and PHP With Docker

2019-04-14 11:00发布

问题:

I've been searching the web and reading manuals and I just can't seem to get my head around what is wrong with my Docker setup.

The Goal

To container-ize Apache, PHP and MySQL allowing them to be customized on a per-project basis. The only dependency to deploy the stack should be docker. All other dependencies / actions should be able to be able to be built / run via Dockerfile.

The Proof of Concept

From my Apache + MySQL + PHP stack via docker-compose.yml file - I'd like to target an index.php page to successfully render Hello Docker! along with a list of all available databases.

The Problem

When I visit docker.dev/index.php in my browser, rather than the PHP code running, I can only view the PHP source code. This is what I see:

<?php

/**
 * This file:
 *     Has not been tested
 *     Does not use prepared statements
 *     Is for Proof of Concept only!
 */

$host = '127.0.0.1';
$user = 'root';
$pass = 'docker';

$conn = new mysqli($host, $user, $pass);

$sql = 'show databases';
$results = $conn->query($sql);

?>

<h1>Hello Docker!</h1>

<ul>
    <?php while ($row = $results->fetch_assoc()) : ?>
        <li><?= $row['Database'] ?></li>
    <?php endwhile ?>
</ul>

My understanding (which may be mistaken) is that Apache is correctly handling the virtual host, but doesn't know to load the PHP file through an Apache PHP Module.

I have setup Apache to depends_on PHP and I have linked them through a network (along with MySQL) but obviously I'm missing something or else everything would be working just as I want it to).

I have created an repo on github that should allow you to test my setup with a few simple commands:

git clone https://github.com/dambrogia/docker-testing.git
cd docker-testing
docker-compose up -d

You will also have to edit add docker.dev to 127.0.0.1 in your hosts file on your host machine!

How can I render the PHP rather than read the source of it when I visit docker.dev/index.php?

I do not want to use a PHP and Apache combined image if at all possible. I would like to have three separate containers - PHP, Apache, MySQL.

回答1:

If you're working with PHP, and you wish to have a single process per container, then I do recommend using Nginx and using PHP-FPM, as it's significantly easier to configure than Apache for this type of setup (at least that's what I've found).

You need to ensure you have a common shared volume to both the Nginx and PHP containers. In that volume you would have your index.php. Here is a crude example docker-compose.yml:

services:
  php7:
    image: "php:7.1.10-fpm"
    container_name: "prefix-php"
    volumes:
      - "./your/local/dir:/var/www/html"
  nginx:
    image: "nginx:1.13.6"
    container_name: "prefix-nginx"
    ports:
      - "80:80"
      - "443:443"
    links:
      - "php7"
    volumes:
      - "./your/local/dir:/var/www/html"

You would then run this following command in the directory where the docker-compose.yml file is:

$ docker-compose -p prefix

The reason for "prefix" is that you create a project grouping for your containers so as not to clash with other container names.

Naturally you then need an nginx site configuration that points to /var/www/html. You will have little to no configuration requirements for the php-fpm container.

A side note regarding the nginx configuration. The above docker-compose.yml is incomplete without the referencing of the php container within the nginx configuration. This would look like so (roughly speaking):

server {
    listen 80 default_server;

    # ...more config, like root, index, server_name, etc

    location ~* \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass prefix-php:9000; # Note the container name here.
        fastcgi_index index.php;
        fastcgi_hide_header X-Powered-By;
        fastcgi_read_timeout 300s;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # ...more rules
}

You'll notice I named the container "php7", you could actually add another "php5" container to this docker-compose.yml and then that allows you to define nginx sites that use different versions of PHP all running on the same docker-compose setup.

I appreciate that this doesn't directly answer your question as it doesn't solve it using apache, but it's an alternative to consider.

Hopefully this at least gives you ideas to help solve your setup.



回答2:

I solved this question and created a repo for anyone who is interested a more in depth explanation or proof of concept.

Please see my repo: https://github.com/dambrogia/docker-testing

TL; DR

The approach I used to solve this was proxying all apache requests to any .php files to PHP-FPM via fcgi://php:9000. Port 9000 is the default

You can see this Apache setting in action here.

The /var/www/html/$1 portion of the setting is where the files are mapped within the PHP container.



回答3:

You've encountered the meme of "containers should do one thing", which is fine, but it does not mean that you ought to split it down this far. A LAMP container is perfectly normal in Docker-land, and I don't know what efforts have been made to split up Apache and PHP - I suspect it is waste of engineering effort.

As you say, you wish to be able to run different PHP versions. That's absolutely fine, and you should customise that in your Dockerfile. If your intention is to build a set of containers that your services can inherit from, then you could simply have a base Apache container, upon which you add PHP in inheriting Dockerfiles.

I run a set of microservices, which use a mix of 5.6 and 7.0. They all inherit from a very plain alpine (of varying versions: 3.5, 3.6 and latest, which will become 3.7). It takes about 15 minutes to copy-and-paste my Dockerfile requirements on top, plus a bit of container tweaking that I'd probably do anyway. So, if a ready-to-run set of containers is your aim, I am not sure how much time you'd be saving in practice.

That all said, if you really want to pursue this, you could look into the mechanism that Piwik uses. I am not familiar with it, but the PHP container serves FastCGI, and that needs to be proxied by a web server, in another container.