docker下nginx反向代理和负载均衡配置

本文已迁移至 segmentfault

1. 镜像安装

可以通过以下命令拉取nginx和tomcat镜像作为测试

[root@docker /]# docker pull nginx
[root@docker /]# docker pull tomcat

2. nginx启动

  • 镜像下载完后,执行以下命令启动nginx
  • [root@docker /]# docker run --name=my_nginx -p 8000:80 -d nginx
    

    --name: 为nginx容器指定一个名称方便管理
    -p: 将nginx内部80端口代理到宿主机8000端口,可以通过宿主机:8000访问nginx 80端口
    -d: 后台运行

  • 可以通过docker ps 命令查看容器运行情况
  • [root@docker /]# docker ps
    CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                            NAMES
    1833fcff605b        nginx                 "nginx -g 'daemon off"   2 minutes ago       Up 2 minutes        0.0.0.0:8000->80/tcp             my_nginx
    
  • 浏览器访问http://192.168.43.32:8000/查看是否能够访问,或者使用curl命令(推荐)
  • [root@docker /]# curl http://192.168.43.32:8000
    <!DOCTYPE html>
    <title>Welcome to nginx!</title>
    

    nginx配置文件

    进入nginx容器后台

    [root@docker /]# docker exec -it my_nginx bash
    root@1833fcff605b:/# cd /etc/nginx/
    root@1833fcff605b:/etc/nginx# ls -l
    total 36
    drwxr-xr-x. 2 root root   26 Dec 26 18:16 conf.d
    -rw-r--r--. 1 root root 1007 Dec 26 11:11 fastcgi_params
    -rw-r--r--. 1 root root 2837 Dec 26 11:11 koi-utf
    -rw-r--r--. 1 root root 2223 Dec 26 11:11 koi-win
    -rw-r--r--. 1 root root 5170 Dec 26 11:11 mime.types
    lrwxrwxrwx. 1 root root   22 Dec 26 11:11 modules -> /usr/lib/nginx/modules
    -rw-r--r--. 1 root root  643 Dec 26 11:11 nginx.conf
    -rw-r--r--. 1 root root  636 Dec 26 11:11 scgi_params
    -rw-r--r--. 1 root root  664 Dec 26 11:11 uwsgi_params
    -rw-r--r--. 1 root root 3610 Dec 26 11:11 win-utf
    

    nginx.conf是nginx主要配置文件,可以通过more命令查看nginx.conf(容器默认不安装vi工具)

    root@1833fcff605b:/etc/nginx# more nginx.conf
    user  nginx;
    worker_processes  1;
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    events {
        worker_connections  1024;
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        sendfile        on;
        #tcp_nopush     on;
        keepalive_timeout  65;
        #gzip  on;
        include /etc/nginx/conf.d/*.conf;
    

    注意到最后一行配置
    include /etc/nginx/conf.d/*.conf;
    include可以将其他配置文件导入,进入/etc/nginx/conf.d/目录下查看

    root@1833fcff605b:/etc/nginx# cd /etc/nginx/conf.d/
    root@1833fcff605b:/etc/nginx/conf.d# ls
    default.conf
    

    default.conf

    root@1833fcff605b:/etc/nginx/conf.d# more default.conf
    server {
        listen       80;
        server_name  localhost;
        #charset koi8-r;
        #access_log  /var/log/nginx/host.access.log  main;
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #location ~ /\.ht {
        #    deny  all;
    

    #表示注释可以忽略
    server:代表虚拟服务器,下面会解释什么是虚拟服务器
    server_name:服务器名称
    location:访问路径匹配规则,这也是nginx最灵活的地方,可以使用正则表达式
    解释下什么是虚拟服务器
    如果给nginx服务器在dns上配置两个域名
    domain1.nginx.com
    domain2.nginx.com
    用户访问这两个域名,你可能希望他们访问同一个后台服务,也可能希望访问不同的后台服务
    那么在nginx里就可以配置两个虚拟服务器也就是两个server节点

    server {
        listen       80;
        server_name  domain1.nginx.com;
    server {
        listen       80;
        server_name  domain2.nginx.com;
    

    两个虚拟服务器监听同一个端口80.nginx可以根据用户访问的host(http头部host)字段代理到不同服务器上

    location->root:静态资源目录,web静态资源如图片,js,html可通过该方式存放
    location->index:如用户未指定请求资源名称,默认访问index指定的文件,如访问http://host:port/html/则默认访问http://host:port/html/index.html

    3. 测试场景

    我们假设有以下三个场景

    宿主机有个目录存储静态资源,需要通过nginx代理出去,用户访问http://host:port/resource/xxxx访问 tomcat服务器(docker容器)上运行一个web程序,需要通过nginx代理出去,web程序context root为WebTestApp 该web程序运行在两个tomcat容器中,需要通过nginx做负载均衡

    4. nginx配置文件挂载

    docker容器每次重启都是一个新的环境,也就是说在docker容器内做的任何修改都将被还原,但nginx的配置文件是在docker容器内,我们如果直接进行修改每次重启后都会被还原,这并不是我们所希望的,所以首先需要将配置文件从docker容器中"搬到"宿主机上,这里会通过docker的卷(volume)实现。
    volume可以将本地文件挂载到docker容器内,这样容器重启后信息不会丢失
    对于nginx,可以将nginx.conf文件和conf.d目录从容器内部"搬"出来
    在本地创建nginx.conf文件和conf.d目录

    [root@docker /]# mkdir -p /u01/nginx
    [root@docker /]# mkdir -p /u01/nginx/conf.d
    [root@docker /]# touch /u01/nginx/nginx.conf
    [root@docker /]# touch /u01/nginx/conf.d/default.conf
    

    nginx.conf文件和default.conf内容可以直接从容器内部复制

    nginx.conf

    user  nginx;
    worker_processes  1;
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    events {
        worker_connections  1024;
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        sendfile        on;
        #tcp_nopush     on;
        keepalive_timeout  65;
        #gzip  on;
        include /etc/nginx/conf.d/*.conf;
    

    default.conf

    server {
        listen       80;
        server_name  localhost;
        #charset koi8-r;
        #access_log  /var/log/nginx/host.access.log  main;
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #location ~ /\.ht {
        #    deny  all;
    

    重新启动nginx

    [root@docker /]# docker stop my_nginx
    my_nginx
    [root@docker /]# docker rm my_nginx
    my_nginx
    [root@docker nginx]# docker run --name=my_nginx -v /u01/nginx/nginx.conf:/etc/nginx/nginx.conf -v /u01/nginx/conf.d:/etc/nginx/conf.d -p 8000:80 -d nginx
    6efe91858f071a50197da104cdccf8500234f1bf6d0f4f56d3dc5de02261272c
    [root@docker /]# curl http://192.168.43.32:8000
    <!DOCTYPE html>
    <title>Welcome to nginx!</title>
    

    注意到执行了一个 docker rm my_nginx 命令,这是因为容器每次启动需要指定不同的名字,一个名字不能使用多次,可以通过 docker ps -a查看所有容器,包括未运行的容器。rm命令可以删除指定容器
    下面正式配置上面三个场景

    5. 场景1-静态资源代理

    在u01目录下创建目录resource,并上传静态资源

    [root@docker resource]# pwd
    /u01/resource
    [root@docker resource]# ll
    total 164
    -rw-r--r--. 1 root root 147291 Oct  8  2015 angular.min.js
    -rw-r--r--. 1 root root  17189 Nov  3 10:32 docker.jpg
    [root@docker resource]#
    

    修改/u01/nginx/conf.d/default.conf文件

    default.conf

    server {
        listen       80;
        server_name localhost;
        location /resource {
            root   /u01;
            index  index.html index.htm;
    

    ps:root是/u01不是/u01/resource。如果root配置成/u01/resource/则nginx会去/u01/resource/resource目录下寻找文件
    重新启动nginx容器,将resource目录挂载到容器内部

    [root@docker u01]# docker stop my_nginx
    my_nginx
    [root@docker u01]# docker rm my_nginx
    my_nginx
    [root@docker u01]# docker run --name=my_nginx -v /u01/nginx/nginx.conf:/etc/nginx/nginx.conf -v /u01/nginx/conf.d:/etc/nginx/conf.d -v /u01/resource:/u01/resource -p 8000:80 -d nginx
    http://192.168.43.32:8000/resource/docker.jpg
    http://192.168.43.32:8000/resource/angular.min.js
    测试配置是否成功(将上面ip替换成宿主机ip)

    6. 场景2-tomcat代理

    准备一个servet作为测试

    package com.df.demo;
    import java.io.IOException;
    import java.io.PrintWriter;
    import javax.servlet.*;
    import javax.servlet.http.*;
    public class WebTestService extends HttpServlet {
        private static final String CONTENT_TYPE = "text/html; charset=UTF-8";
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
        public void doGet(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException,
                                                               IOException {
            response.setContentType(CONTENT_TYPE);
            PrintWriter out = response.getWriter();
            out.print("hello docker tomcat");
            out.close();
    

    打成war包,包名为WebTestApp.war,在u01上创建webapps目录并上传war包

    [root@docker webapps]# pwd
    /u01/webapps
    [root@docker webapps]# ls
    WebTestApp.war
    

    启动tomcat容器,并将/u01/webapps目录挂载到tomcat的/usr/local/tomcat/webapps目录下

    [root@docker ~]# docker run --name=my_tomcat1 -v /u01/webapps:/usr/local/tomcat/webapps -p 8001:8080 -d tomcat
    [root@docker ~]# curl http://localhost:8001/WebTestApp
    hello docker tomcat
    

    程序已经成功部署到tomcat上
    修改/u01/nginx/conf.d/default.conf文件

    default.conf

    server {
        listen       80;
        server_name localhost;
        location / {
            proxy_pass   http://tomcat_server;
    

    修改/u01/nginx/nginx.conf在http配置节点中增加以下配置

    upstream tomcat_server {
          server t1:8080;
    

    upstream 可以定义一组服务器
    proxy_pass 设置代理的服务器,格式为http://upstream_name

    重启nginx容器,这里我们需要使用一个新的docker参数--link

    [root@docker u01]# docker run --name=my_nginx1 --link=my_tomcat1:t1 -v /u01/nginx/nginx.conf:/etc/nginx/nginx.conf -v /u01/nginx/conf.d:/etc/nginx/conf.d -p 8000:80 -d nginx
    [root@docker ~]# curl http://192.168.43.32:8000/WebTestApp
    hello docker tomcat
    

    ps:这里换了一个名字my_nginx1,用原来的名字my_nginx无法启动容器,会报以下错误

    2018/01/27 08:40:44 [emerg] 1#1: host not found in upstream "t1:8080" in /etc/nginx/nginx.conf:30
    nginx: [emerg] host not found in upstream "t1:8080" in /etc/nginx/nginx.conf:30
    

    t1无法找到,但如果不使用--name指定名称可正常启动,原因未知。
    link参数可以在两个容器之间建立网络连接,格式为--link=my_tomcat1:t1 my_tomcat1为容器名称,t1为取的别名
    可登录nginx容器查看/etc/hosts文件,docker会将t1加入到hosts文件中

    [root@docker ~]# docker exec -it my_nginx1 bash
    root@fa5f782b9448:/# more /etc/hosts
    127.0.0.1   localhost
    ::1 localhost ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    172.17.0.5  t1 1f9b1d432ab0 my_tomcat1
    172.17.0.6  fa5f782b9448
    

    7. 场景3-tomcat负载均衡

  • 修改/u01/nginx/nginx.conf在场景2的基础上增加新的服务器
  • upstream tomcat_server {
          server t1:8080;
          server t2:8080;
    
  • 在启动一个tomcat容器my_tomcat2
  • [root@docker ~]# docker run --name=my_tomcat2 -v /u01/webapps:/usr/local/tomcat/webapps -d tomcat
    
  • 重启nginx
  • [root@docker /]# docker stop my_nginx1
    [root@docker /]# dcoker rm my_nginx1
    [root@docker /]# docker run --name=my_nginx1 --link=my_tomcat1:t1 --link=my_tomcat2:t2 -v /u01/nginx/nginx.conf:/etc/nginx/nginx.conf -v /u01/nginx/conf.d:/etc/nginx/conf.d -p 8000:80 -d nginx
    [root@docker ~]# curl http://192.168.43.32:8000/WebTestApp