- PHP Microservices
- Carlos Pérez Sánchez Pablo Solar Vilari?o
- 2381字
- 2021-07-09 18:50:05
Setting up a development environment for microservices
One of the greatest benefits of using Docker and its container ecosystem is that you don't need to install anything else on your machine. For example, if you need a MySQL database, you don't need to install anything on your local dev; it is easier to spin a container with the version you want and start using it.
This way of developing is more flexible, therefore we will be working with Docker containers throughout the whole book. In this section, we will learn how to build a basic Docker environment; it will be our foundation and we will be improving and adapting this base to each of our microservices in the subsequent chapters.
To simplify the folder structure of our project, we will have some root folders on our development machine:
Docker
: This folder will contain all the Docker environmentSource
: This folder will have the source of each of our microservices
Note that this structure is flexible and can be changed and adapted to your specific requirements without any problems.
All the required files are available on our GitHub repository, at https://github.com/php-microservices/docker, on the master branch with the chapter-02
tag.
Let's dig deeper into the Docker setup. Open your docker folder and create a file called docker-compose.yml
with the following content:
version: '2' services:
These two lines indicate that we are using the latest syntax for Docker compose and they define a list of services that we will be spinning every time we do a docker-compose up
. All our services will be added after the services
declaration.
Autodiscovery service
Autodiscovery is a mechanism in which we don't specify the endpoints of each of our microservices. Each one of our services use a shared registry in which they say that they are available. When a microservice needs to know the location of another microservice, it can consult our autodiscovery registry to know the required endpoint.
For our application, we will be using an autodiscovery mechanism to ensure that our microservices can be scaled easily and if a node is not healthy, we stop sending requests to it. Our choice for this purpose is to use Consul (by HashiCorp), a very small application that we can add to our project. The main role for our Consul container is to keep everything in order, keeping a list of the available and healthy services.
Let's start the project by opening your docker-compose.yml
file with your favorite IDE/editor and adding the next piece of code just after the services:
line:
version: '2' services: autodiscovery: build: ./autodiscovery/ mem_limit: 128m expose: - 53 - 8300 - 8301 - 8302 - 8400 - 8500 ports: - 8500:8500 dns: - 127.0.0.1
In a Docker compose file, the syntax is very easy to understand and always follows the same flow. The first line defines a container type (it is like a class name for devs); in our case it is autodiscovery
, and inside this container type we can specify several options to adapt the container to our requirements.
With build: ./autodiscovery/
, we are telling Docker where it can find a Dockerfile that describes what we want in our container in detail.
The mem_limit: 128m
sentence will limit the memory consumption of any container of the autodiscovery
type to not more than 128 Mb. Note that this instruction is optional.
Each container needs different ports open and, by default, when you spin a container, none of them are open. For this reason, you need to specify which ports you want open for each container. For example, a container with a web server will need the port 80
open but for a container that runs MySQL, the required port may be 3306
. In our case, we are opening the ports 53
, 8300
, 8301
, 8302
, 8400
, and 8500
for each one of our autodiscovery
containers.
If you try to reach the container on one of the opened ports, it will not work. The container ecosystem resides in a separate network and you can only access it if you create a bridge between your environment and the Docker network. Our autodiscovery
container runs Consul and it has a nice web UI on port 8500
. We want to be able to use this UI; so, when we use ports
, we are mapping our local 8500
port to the container 8500
port.
Now, it's time to create a new folder called autodiscovery
in the same path of your docker-compose.yml file
. Inside this new folder, place a file called Dockerfile
with the following line:
FROM consul:v0.7.0
This small sentence inside the Dockerfile
indicates that we are using a Docker consul
image with tag v0.7.0
. This image will be fetched from the official Docker hub, a repository for container images.
At this point, doing a $ docker-compose up
will spin up a Consul machine, give it a try. Since we didn't specify the -d
option, the Docker engine will output all the logs to your terminal. You can stop your container with a simple CTRL+C. When you add the -d
option, the Docker compose runs as a daemon and returns the prompt; you can do a $ docker-compose stop
to stop the containers.
Microservice base core - NGINX and PHP-FPM
PHP-FPM is an alternative to the old way of executing PHP in our web server. The main benefit of using PHP-FPM is its small memory footprint and the high performance under hight loads. The best web server you can find nowadays to run your PHP-FPM is NGINX, a very light web server and reverse proxy used in the most important projects.
Since our application will be using an autodiscovery pattern, we need an easy way of dealing with the service registering, deregistering, and health check. One of the simplest and fastest applications you can use is ContainerPilot, a small micro-orchestration application created by Joyent that works with your favorite container scheduler, in our case Docker compose. This small app is being executed as PID 1 and forks the application we want to run inside the container.
We will be working with ContainerPilot because it relieves the developer of dealing with the autodiscovery, so we need to have the latest version on each container we will be using.
Let's start defining our base php-fpm
container. Open the docker-compose.yml
and add a new service for the php-fpm
:
microservice_base_fpm: build: ./microservices/base/php-fpm/ links: - autodiscovery expose: - 9000 environment: - BACKEND=microservice_base_nginx - CONSUL=autodiscovery
In the preceding code, we are defining a new service and one interesting attribute is links. This attribute defines which other containers our service can see or connect. In our example, we want to link this type of container to any autodiscovery
container. Without this explicit definition, our fpm
container won't see the autodiscovery
service.
Now, create the microservices/base/php-fpm/Dockerfile
file on your IDE/editor with the following content:
FROM php:7-fpm RUN apt-get update && apt-get -y install \ git g++ libcurl4-gnutls-dev libicu-dev libmcrypt-dev libpq-dev libxml2-dev unzip zlib1g-dev \ && git clone -b php7 https://github.com/phpredis/phpredis.git /usr/src/php/ext/redis \ && docker-php-ext-install curl intl json mbstring mcrypt pdo pdo_pgsql redis xml \ && apt-get autoremove && apt-get autoclean \ && rm -rf /var/lib/apt/lists/* RUN echo 'date.timezone="Europe/Madrid"' >> /usr/local/etc/php/conf.d/date.ini RUN echo 'session.save_path = "/tmp"' >> /usr/local/etc/php/conf.d/session.ini ENV CONSUL_TEMPLATE_VERSION 0.16.0 ENV CONSUL_TEMPLATE_SHA1 064b0b492bb7ca3663811d297436a4bbf3226de706d2b76adade7021cd22e156 RUN curl --retry 7 -Lso /tmp/consul-template.zip \ "https://releases.hashicorp.com/ consul-template/${CONSUL_TEMPLATE_VERSION}/ consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip" \ && echo "${CONSUL_TEMPLATE_SHA1} /tmp/consul-template.zip" | sha256sum -c \ && unzip /tmp/consul-template.zip -d /usr/local/bin \ && rm /tmp/consul-template.zip ENV CONTAINERPILOT_VERSION 2.4.3 ENV CONTAINERPILOT_SHA1 2c469a0e79a7ac801f1c032c2515dd0278134790 ENV CONTAINERPILOT file:///etc/containerpilot.json RUN curl --retry 7 -Lso /tmp/containerpilot.tar.gz \ "https://github.com/joyent/containerpilot/releases/download/ ${CONTAINERPILOT_VERSION}/containerpilot- ${CONTAINERPILOT_VERSION}.tar.gz" \ && echo "${CONTAINERPILOT_SHA1} /tmp/containerpilot.tar.gz" | sha1sum -c \ && tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \ && rm /tmp/containerpilot.tar.gz COPY config/ /etc COPY scripts/ /usr/local/bin RUN chmod +x /usr/local/bin/reload.sh CMD [ "/usr/local/bin/containerpilot", "php-fpm", "--nodaemonize"]
What we have done on this file is tell Docker how it needs to create our php-fpm
container. The first line declares the official version we want to use as a foundation for our container, in this case php7 fpm. Once the image is downloaded, the first RUN
line will add all the extra PHP
packages we will be using.
The two RUN
sentences will add bespoke PHP configurations; feel free to adapt these lines to your requirements.
Once all the PHP tasks are done, it is time to install a small application on the container that will help us to deal with templates--consul-template
. This application is used to build configuration templates on the fly using the information we have stored on our Consul service.
As we said before, we are using ContainerPilot. So, after the consul-template
installation, we are telling Docker how to install this application.
At this point, Docker finishes installing all the required packages and copies some configuration and shell scripts needed by ContainerPilot
The last line starts ContainerPilot as PID 1 and forks php-fpm
.
Now, let's explain the configuration file required by ContainerPilot. Open your IDE/editor and create the microservices/base/php-fpm/config/containerpilot.json
file with the following content:
{ "consul": "{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL }} {{ end }}:8500", "preStart": "/usr/local/bin/reload.sh preStart", "logging": {"level": "DEBUG"}, "services": [ { "name": "microservice_base_fpm", "port": 80, "health": "/usr/local/sbin/php-fpm -t", "poll": 10, "ttl": 25, "interfaces": ["eth1", "eth0"] } ], "backends": [ { "name": "{{ .BACKEND }}", "poll": 7, "onChange": "/usr/local/bin/reload.sh" } ], "coprocesses": [{{ if .CONSUL_AGENT }} { "command": ["/usr/local/bin/consul", "agent", "-data-dir=/var/lib/consul", "-config-dir=/etc/consul", "-rejoin", "-retry-join", "{{ .CONSUL }}", "-retry-max", "10", "-retry-interval", "10s"], "restarts": "unlimited" } {{ end }}] }
This JSON configuration file is very easy to understand. First, it defines where we can find our Consul container and which command we want to run on the ContainerPilot preStart event. In services
, you can define all the services you want to declare that the current container is running. On the backends
, you can define all the services you are listening for changes. In our case, we are listening for changes to services called microservice_base_nginx
(the BACKEND
variable is defined on the docker-compose.yml
). If something changes on Consul on these services, we will execute the onChange
command in the container.
For a more information about ContainerPilot, you can visit the official page, that is, https://www.joyent.com/containerpilot.
It's time to create the microservices/base/php-fpm/scripts/reload.sh
file with the following content:
#!/bin/bash SERVICE_NAME=${SERVICE_NAME:-php-fpm} CONSUL=${CONSUL:-consul} preStart() { echo "php-fpm preStart" } onChange() { echo "php-fpm onChange" } help() { echo "Usage: ./reload.sh preStart => first-run configuration for php-fpm" echo " ./reload.sh onChange => [default] update php-fom config on upstream changes" } until cmd=$1 if [ -z "$cmd" ]; then onChange fi shift 1 $cmd "$@" [ "$?" -ne 127 ] do onChange exit done
Here, we created a dummy script, but it is up to you to adapt it to your requirements. For example, it can be changed to run execute consul-template
and rebuild the NGINX configuration once ContainerPilot fires the script. We will be explaining a more complex script later.
We have our base php-fpm
container ready, but our basic environment can't be complete without a web server. We will be using NGINX, a very light and powerful reverse proxy and web server.
The way we will build our NGINX server is very similar to the php-fpm
, so we will only explain the differences.
Tip
Remember that all the files are available in our GitHub repository.
We will add a new service definition for NGINX to the docker-compose.yml
file and link it to our autodiscovery
service and also to our php-fpm
:
microservice_base_nginx: build: ./microservices/base/nginx/ links: - autodiscovery - microservice_base_fpm environment: - BACKEND=microservice_base_fpm - CONSUL=autodiscovery ports: - 8080:80
In our microservices/base/nginx/config/containerpilot.json
, we now have a new option telemetry
. This config setting allows us to specify a remote telemetry service used to collect stats from our service. Having this kind of service included in our environment allows us to see how our containers are performing:
"telemetry": { "port": 9090, "sensors": [ { "name": "nginx_connections_unhandled_total", "help": "Number of accepted connnections that were not handled", "type": "gauge", "poll": 5, "check": ["/usr/local/bin/sensor.sh", "unhandled"] }, { "name": "nginx_connections_load", "help": "Ratio of active connections (less waiting) to the maximum worker connections", "type": "gauge", "poll": 5, "check": ["/usr/local/bin/sensor.sh", "connections_load"] } ] }
As you can see, we use a bespoke bash script to obtain the container stats, and the content of our microservices/base/nginx/scripts/sensor.sh
script is as follows:
#!/bin/bash set -e help() { echo 'Make requests to the Nginx stub_status endpoint and pull out metrics' echo 'for the telemetry service. Refer to the Nginx docs for details:' echo 'http://nginx.org/en/docs/http/ngx_http_stub_status_module.html' } unhandled() { local accepts=$(curl -s --fail localhost/nginx-health | awk 'FNR == 3 {print $1}') local handled=$(curl -s --fail localhost/nginx-health | awk 'FNR == 3 {print $2}') echo $(expr ${accepts} - ${handled}) } connections_load() { local scraped=$(curl -s --fail localhost/nginx-health) local active=$(echo ${scraped} | awk '/Active connections/{print $3}') local waiting=$(echo ${scraped} | awk '/Reading/{print $6}') local workers=$(echo $(cat /etc/nginx/nginx.conf | perl -n -e'/ worker_connections *(\d+)/ && print $1') ) echo $(echo "scale=4; (${active} - ${waiting}) / ${workers}" | bc) } cmd=$1 if [ ! -z "$cmd" ]; then shift 1 $cmd "$@" exit fi help
This bash script gets some nginx
stats that we will be sending to our telemetry
server with ContainerPilot.
Our microservices/base/nginx/scripts/reload.sh
is a little more complex than the one we created before for php-fpm
:
#!/bin/bash SERVICE_NAME=${SERVICE_NAME:-nginx} CONSUL=${CONSUL:-consul} preStart() { consul-template \ -once \ -dedup \ -consul ${CONSUL}:8500 \ -template "/etc/nginx/nginx.conf.ctmpl:/etc/nginx/nginx.conf" } onChange() { consul-template \ -once \ -dedup \ -consul ${CONSUL}:8500 \ -template "/etc/nginx/nginx.conf.ctmpl:/etc/nginx/ nginx.conf:nginx -s reload" } help() { echo "Usage: ./reload.sh preStart => first-run configuration for Nginx" echo " ./reload.sh onChange => [default] update Nginx config on upstream changes" } until cmd=$1 if [ -z "$cmd" ]; then onChange fi shift 1 $cmd "$@" [ "$?" -ne 127 ] do onChange exit done
As you can see, we use consul-template
to rebuild our NGINX config on the startup or when ContainerPilot detects a change in the list of backend services we will be monitoring. This behavior allows us to stop sending requests to unhealthy nodes.
At this point, we have our base environment ready and we are ready to test it with a simple $ docker-compose up
. We will be using all these pieces to create bigger and more complex services. In the upcoming chapters, we will be adding the telemetry service or a data storage among others.
- 信息可視化的藝術(shù):信息可視化在英國
- Scala Design Patterns
- Hands-On Data Structures and Algorithms with JavaScript
- 編寫整潔的Python代碼(第2版)
- Practical Windows Forensics
- 微信小程序開發(fā)解析
- 實戰(zhàn)Java高并發(fā)程序設(shè)計(第3版)
- D3.js 4.x Data Visualization(Third Edition)
- Android Wear Projects
- 汽車人機(jī)交互界面整合設(shè)計
- Mastering Apache Storm
- ActionScript 3.0從入門到精通(視頻實戰(zhàn)版)
- 精益軟件開發(fā)管理之道
- 劍指大數(shù)據(jù):企業(yè)級電商數(shù)據(jù)倉庫項目實戰(zhàn)(精華版)
- Fast Data Processing with Spark 2(Third Edition)