分类 Docker 下的文章

配置nginx为php-fpm合并多个X-Forwarded-For

最近用阿里云的容器服务遇到一个问题,docker里的PHP程序用X-Forwarded-For取用户IP的时候,只能取到SLB的地址。经tcpdump抓包确认,是请求经过阿里云的acsrouting的时候,在HTTP请求头中加入了多个X-Forwarded-For。查了一下,阿里云用HAProxy做负载均衡服务,又搜了一下HAProxy的资料,发现许多人在用HAProxy时遇到了同类问题,有人给HAProxy提了issue,但对方答复是根据RFC2616,多个同名的header和单个逗号分隔列表构成的header是等价的:

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

所以HAProxy不处理已有的X-Forwarded-For,只是简单地在header末尾加入一个新的X-Forwarded-For。可惜php-fpm对header的处理方式是同名的header只保留最后一个,所以无法取得正确的用户IP。

我找到一个解决方案是让nginx来合并多个X-Forwarded-For记录。在docker内的nginx配置中加入以下选项(一般在location ~ \.php$部分或者在fastcgi_params配置文件里):

fastcgi_param  HTTP_X_FORWARDED_FOR $http_x_forwarded_for if_not_empty;

配置好之后docker内的nginx服务器就会预先合并多个X-Forwarded-For请求header记录为逗号分隔格式,然后再传给php-fpm。

PHP 7.0.16 在阿里云容器服务中Could not gather sufficient random data问题的解决办法

最近一个在公司内部环境测试正常的PHP7.0.16容器发布到阿里云容器服务后,调用random_bytes函数回报Could not gather sufficient random data错误。上网搜了一圏看到是这个Patch导致的。如果PHP在新版系统内核中编译,PHP会使用新版内核中提供的getrandom调用来取得随机数,而不会读取/dev/urandom,如果把它移到旧版内核的系统上运行,random_intrandom_bytes函数就会报Could not gather sufficient random data。

阿里云的容器服务默认用的是Ubuntu 14.04 3.13.60-generic版本的内核,我的PHP容器是在Ubuntu 16.04 4.9.12-moby内核中编译构建的,所以会触发这个问题。解决方法是升级阿里云的容器服务节点,把内核升级到4.x之后此问题就没有发生了。希望未来的PHP版本会提供getrandom失败后的fallback方案。

清理mac上Docker占用的磁盘空间

Docker依赖Linux系统的cgroup实现,在mac系统中运行的时候,Docker会启动一个虚拟机中的Linux内核,并在硬盘上放一个qcow2格式的磁盘镜像文件。这个文件会随着Docker的使用不断膨胀,即使删除不用的Docker Image和Container也不会缩小。我在测试完一个自动化工具的Dockerfile改写之后,Docker.qcow2文件就达到42GB了。

Google搜了一圏发现一个叫Théo Chamley的法国人写了一个脚本释放Docker.qcow2文件占用的空间。基本原理是用docker save命令保存要保留的image,然后关闭Docker,删除Docker.qcow2,再启动Docker,它会自动重建,最后用docker load命令恢复保留的image。

下面是这哥儿们写的脚本。

#!/bin/bash

# Copyright 2017 Théo Chamley
# Permission is hereby granted, free of charge, to any person obtaining a copy of 
# this software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
# to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

IMAGES=$@

echo "This will remove all your current containers and images except for:"
echo ${IMAGES}
read -p "Are you sure? [yes/NO] " -n 1 -r
echo    # (optional) move to a new line
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
    exit 1
fi


TMP_DIR=$(mktemp -d)

pushd $TMP_DIR >/dev/null

open -a Docker
echo "=> Saving the specified images"
for image in ${IMAGES}; do
    echo "==> Saving ${image}"
    tar=$(echo -n ${image} | base64)
    docker save -o ${tar}.tar ${image}
    echo "==> Done."
done

echo "=> Cleaning up"
echo -n "==> Quiting Docker"
osascript -e 'quit app "Docker"'
while docker info >/dev/null 2>&1; do
    echo -n "."
    sleep 1
done;
echo ""

echo "==> Removing Docker.qcow2 file"
rm ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2

echo "==> Launching Docker"
open -a Docker
echo -n "==> Waiting for Docker to start"
until docker info >/dev/null 2>&1; do
    echo -n "."
    sleep 1
done;
echo ""

echo "=> Done."

echo "=> Loading saved images"
for image in ${IMAGES}; do
    echo "==> Loading ${image}"
    tar=$(echo -n ${image} | base64)
    docker load -q -i ${tar}.tar || exit 1
    echo "==> Done."
done

popd >/dev/null
rm -r ${TMP_DIR}

Théo Chamley的博客原文