web-dev-qa-db-ja.com

Nginxをアップストリームプロキシとして使用するようにDockerポートマッピングを構成する方法

アップデートII

2015年7月16日であり、状況は再び変化しました。 Jason Wilderhttps://github.com/jwilder/nginx-proxy からこの自動魔法のコンテナを発見し、docker runになるまでこの問題を解決します。コンテナ。これが、この問題を解決するために使用しているソリューションです。

更新

現在は2015年7月であり、Dockerコンテナーのネットワーキングに関しては大きく変化しています。現在、この問題を(さまざまな方法で)解決する多くの異なる製品があります。

この投稿を使用して、サービス検出への docker --link アプローチの基本的な理解を得る必要があります。他のほとんどのソリューションよりも。特定のクラスター内の個別のホストでコンテナーをネットワーク化することは非常に難しく、コンテナーをネットワーク化すると再起動できませんが、同じホスト上のコンテナーをネットワーク化するための迅速かつ比較的簡単な方法を提供します。これは、この問題を解決するために使用する可能性のあるソフトウェアが実際に内部で何をしているのかを知る良い方法です。

さらに、おそらくDockerの初期 network 、Hashicorpの consul 、Weaveworks も確認する必要があります。 weave 、Jeff Lindsayの progrium/consulgliderlabs/registrator 、およびGoogleの Kubernetes

CoreOS オファリングもあり、 etcdfleet 、および flannel

本当にパーティーをしたい場合は、クラスターを起動して Mesosphere 、または Deis 、または Flynn

ネットワーキングに慣れていない場合(私のように)、老眼鏡を取り出して、ポップ 「Wi-Hiで星に星を塗る-The Best of Enya」 -Fi、そしてビールを割る-それはあなたが本当に何をしようとしているのかを正確に理解するまでしばらくするでしょう。ヒント:Service Discovery LayerCluster Control Planeを実装しようとしています。土曜日の夜を過ごすにはとてもいい方法です。

それはとても楽しいですが、すぐに飛び込む前に、一般的なネットワークについて自分自身をより良く教育するために時間をかけたいと思います。最終的に、慈悲深いデジタルオーシャンチュートリアルの神々からのいくつかの投稿を見つけました: Introduction to Networking Terminology および Understanding ... Networking 。飛び込む前にそれらを数回読むことをお勧めします。

楽しむ!



元の投稿

Dockerコンテナのポートマッピングを把握できないようです。具体的には、同じサーバー上の別のポートでリッスンして、Nginxから別のコンテナーにリクエストを渡す方法。

Nginxコンテナー用のDockerfileがあります。

FROM ubuntu:14.04
MAINTAINER Me <[email protected]>

RUN apt-get update && apt-get install -y htop git nginx

ADD sites-enabled/api.myapp.com /etc/nginx/sites-enabled/api.myapp.com
ADD sites-enabled/app.myapp.com /etc/nginx/sites-enabled/app.myapp.com
ADD nginx.conf /etc/nginx/nginx.conf

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

EXPOSE 80 443

CMD ["service", "nginx", "start"]



そして、api.myapp.com設定ファイルは次のようになります。

upstream api_upstream{

    server 0.0.0.0:3333;

}


server {

    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;

}


server {

    listen 443;
    server_name api.mypp.com;

    location / {

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $Host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;

    }

}

そして、app.myapp.comについても同様です。

そして、私は実行します:

Sudo docker run -p 80:80 -p 443:443 -d --name Nginx myusername/nginx


そしてそれはすべて正常に立ち上がっていますが、リクエストは他のコンテナ/ポートにパススルーされていません。そして、Nginxコンテナーにsshしてログを検査すると、エラーは表示されません。

何か助け?

81
AJB

@ T0xicCodeの答え は正しいのですが、実際に有効なソリューションを実装するのに実際に約20時間かかったため、詳細を詳しく説明すると思いました。

独自のコンテナでNginxを実行し、それをリバースプロキシとして使用して同じサーバーインスタンス上の複数のアプリケーションの負荷を分散する場合は、次の手順を実行する必要があります。

コンテナをリンクする

通常、docker runにシェルスクリプトを入力することで、コンテナをUser Dataすると、他のrunningコンテナへのリンクを宣言できます。これは、コンテナを順番に起動する必要があり、後者のコンテナのみが前者のコンテナにリンクできることを意味します。そのようです:

#!/bin/bash
Sudo docker run -p 3000:3000 --name API mydockerhub/api
Sudo docker run -p 3001:3001 --link API:API --name App mydockerhub/app
Sudo docker run -p 80:80 -p 443:443 --link API:API --link App:App --name Nginx mydockerhub/nginx

したがって、この例では、APIname__コンテナーは他のどのコンテナーにもリンクされていませんが、Appname__コンテナーはAPIname__にリンクされ、Nginxname__はAPIname__とAppname__の両方にリンクされます。

この結果、envvarsおよびAPIname__およびAppname__コンテナー内にある/etc/hostsファイルが変更されます。結果は次のようになります。

/ etc/hosts

Nginxname__コンテナー内でcat /etc/hostsを実行すると、次の結果が生成されます。

172.17.0.5  0fd9a40ab5ec
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.3  App
172.17.0.2  API



ENV Vars

envname__コンテナー内でNginxname__を実行すると、次の結果が生成されます。

API_PORT=tcp://172.17.0.2:3000
API_PORT_3000_TCP_PROTO=tcp
API_PORT_3000_TCP_PORT=3000
API_PORT_3000_TCP_ADDR=172.17.0.2

APP_PORT=tcp://172.17.0.3:3001
APP_PORT_3001_TCP_PROTO=tcp
APP_PORT_3001_TCP_PORT=3001
APP_PORT_3001_TCP_ADDR=172.17.0.3

実際の変数の多くは切り捨てましたが、上記はコンテナへのトラフィックをプロキシするために必要なキー値です。

実行中のコンテナ内で上記のコマンドを実行するシェルを取得するには、次を使用します。

Sudo docker exec -i -t Nginx bash

これで、リンクされたコンテナのいずれかのローカルIPアドレスを含む/etc/hostsファイルエントリとenvvarの両方があることがわかります。私が知る限り、リンクオプションを宣言してコンテナを実行すると、これがすべてです。ただし、この情報を使用して、nginxname__コンテナー内でNginxname__を構成できます。



Nginxの設定

ここで少し注意が必要です。いくつかのオプションがあります。 dockername__が作成した/etc/hostsファイル内のエントリを指すようにサイトを構成することも、ENVvarsを利用して、nginx.confおよびその他のconfファイルで文字列置換(sedname__を使用)を実行することもできますIP値を挿入する/etc/nginx/sites-enabledフォルダー。



オプションA:ENV Varsを使用してNginxを構成する

/etc/hostsファイルオプションを機能させることができなかったので、これは私が行ったオプションです。すぐにオプションBを試して、調査結果があればこの投稿を更新します。

このオプションと/etc/hostsファイルオプションを使用する主な違いは、Dockerfilename__引数としてシェルスクリプトを使用してCMDname__引数としてシェルスクリプトを使用する方法です。これにより、文字列置換が処理され、IP値がENVname__からconfファイルにコピーされます。

構成ファイルのセットは次のとおりです。

Dockerfile

FROM ubuntu:14.04
MAINTAINER Your Name <[email protected]>

RUN apt-get update && apt-get install -y nano htop git nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD api.myapp.conf /etc/nginx/sites-enabled/api.myapp.conf
ADD app.myapp.conf /etc/nginx/sites-enabled/app.myapp.conf
ADD Nginx-Startup.sh /etc/nginx/Nginx-Startup.sh

EXPOSE 80 443

CMD ["/bin/bash","/etc/nginx/Nginx-Startup.sh"]

nginx.conf

daemon off;
user www-data;
pid /var/run/nginx.pid;
worker_processes 1;


events {
    worker_connections 1024;
}


http {

    # Basic Settings

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 33;
    types_hash_max_size 2048;

    server_tokens off;
    server_names_hash_bucket_size 64;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;


    # Logging Settings
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;


    # Gzip Settings

gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 3;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/xml text/css application/x-javascript application/json;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    # Virtual Host Configs  
    include /etc/nginx/sites-enabled/*;

    # Error Page Config
    #error_page 403 404 500 502 /srv/Splash;


}

注:起動直後にコンテナが終了しないように、daemon off;nginx.confファイルに含めることが重要です。

api.myapp.conf

upstream api_upstream{
    server APP_IP:3000;
}

server {
    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;
}

server {
    listen 443;
    server_name api.myapp.com;

    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $Host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;
    }

}

Nginx-Startup.sh

#!/bin/bash
sed -i 's/APP_IP/'"$API_PORT_3000_TCP_ADDR"'/g' /etc/nginx/sites-enabled/api.myapp.com
sed -i 's/APP_IP/'"$APP_PORT_3001_TCP_ADDR"'/g' /etc/nginx/sites-enabled/app.myapp.com

service nginx start

nginx.confapi.myapp.confのほとんどの内容について宿題をするのはあなたにお任せします。

魔法はNginx-Startup.shで発生し、sedname__を使用してAPP_IPおよびapi.myapp.confファイルのupstreamname__ブロックに書き込んだapp.myapp.confプレースホルダーで文字列置換を行います。

このask.ubuntu.comの質問は非常にうまく説明しています: コマンドを使用してファイル内のテキストを検索して置換

GOTCHAOSXでは、sedname__はオプションを異なる方法で処理します。具体的には-iフラグです。 Ubuntuでは、-iフラグが置換を「インプレース」で処理します。ファイルを開き、テキストを変更してから、同じファイルを「上書き保存」します。 OSXでは、-iフラグには、結果のファイルに含めるファイル拡張子が必要です。拡張子のないファイルを使用している場合は、-iフラグの値として ''を入力する必要があります。

GOTCHA置換したい文字列を見つけるためにsedname__が使用する正規表現内でENV varsを使用するには、varを二重引用符で囲む必要があります。そのため、奇妙な見た目ですが、正しい構文は上記のとおりです。

そのため、Dockerはコンテナを起動し、Nginx-Startup.shスクリプトを実行してトリガーしました。sedname__を使用して、値APP_IPENVname__コマンドで指定した対応するsedname__変数に変更しました。これで、/etc/nginx/sites-enabledディレクトリー内に、コンテナーの起動時にdockerが設定したENVvarsからのIPアドレスを持つconfファイルができました。 api.myapp.confファイル内で、upstreamname__ブロックが次のように変更されていることがわかります。

upstream api_upstream{
    server 172.0.0.2:3000;
}

表示されるIPアドレスは異なる場合がありますが、通常は172.0.0.xであることに気付きました。

これで、すべてが適切にルーティングされるはずです。

GOTCHA最初のインスタンス起動を実行すると、コンテナを再起動/再実行することはできません。 Dockerは、起動時に各コンテナに新しいIPを提供し、以前使用していたものを再利用しないようです。したがって、api.myapp.comは最初に172.0.0.2を取得しますが、次に172.0.0.4を取得します。ただし、Nginxname__は、最初のIPをconfファイルまたは/etc/hostsファイルに既に設定しているため、api.myapp.comの新しいIPを特定できません。これに対する解決策は、CoreOSname__およびそのetcdname__サービスを使用する可能性があります。これは、私の限られた理解では、同じENVname__クラスターに登録されたすべてのマシンに対して共有CoreOSname__のように機能します。これは、セットアップで遊ぶ次のおもちゃです。



オプションB:/etc/hostsファイルエントリを使用

このは、これを行うためのより迅速で簡単な方法である必要がありますが、動作させることができませんでした。表向きは、/etc/hostsエントリの値をapi.myapp.confおよびapp.myapp.confファイルに入力するだけですが、このメソッドを機能させることができませんでした。

UPDATE:このメソッドを機能させる方法については、 @ Wes Todの答え を参照してください。

api.myapp.confで行った試みは次のとおりです。

upstream api_upstream{
    server API:3000;
}

/etc/hostsファイルに次のようなエントリがあることを考えると、172.0.0.2 API値を取得するだけだと思いましたが、そうではないようです。

また、すべてのAZからのElastic Load Balancerソーシングに関するいくつかの補助的な問題があったため、このルートを試したときに問題になった可能性があります。代わりに、Linuxで文字列の置換を処理する方法を学ぶ必要があったので、それは楽しかったです。しばらくしてこれを試してみて、どのようになるかを確認します。

55
AJB

私はコードマジックですべての人に機能する人気のあるJason Wilderリバースプロキシを使用してみましたが、すべての人(つまり、私)には機能しないことがわかりました。そして、私はNGINXを初めて使用するので、使用しようとしているテクノロジーを理解していなかったことが好きではありませんでした。

上記のlinkingコンテナーに関する議論は、廃止された機能であるため、現在は日付が付けられているため、2セントを追加したいと考えました。そこで、networksを使用してそれを行う方法について説明します。この回答は、Docker Composeおよびnginx構成を使用して、静的にページングされたWebサイトへのリバースプロキシとしてnginxを設定する完全な例です。

TL; DR;

互いに通信する必要があるサービスを事前定義されたネットワークに追加します。 Dockerネットワークに関する段階的な議論のために、ここでいくつかのことを学びました: https://technologyconversations.com/2016/04/25/docker-networking-and-dns-the-good-the- bad-and-the-ugly /

ネットワークを定義する

まず、すべてのバックエンドサービスが通信できるネットワークが必要です。私はwebを呼び出しましたが、それは何でも構いません。

docker network create web

アプリを構築する

単純なWebサイトアプリを作成します。 Webサイトは、nginxコンテナーによって提供される単純なindex.htmlページです。コンテンツは、フォルダーcontentの下のホストにマウントされたボリュームです

DockerFile:

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf

default.conf

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web

services:
  nginx:
    container_name: sample-site
    build: .
    expose:
      - "80"
    volumes:
      - "./content/:/var/www/html/"
    networks:
      default: {}
      mynetwork:
        aliases:
          - sample-site

ここでポートマッピングが不要になることに注意してください。ポート80を単純に公開します。これは、ポートの衝突を回避するのに便利です。

アプリを実行する

このウェブサイトを起動します

docker-compose up -d

コンテナのDNSマッピングに関するいくつかの楽しいチェック:

docker exec -it sample-site bash
ping sample-site

このpingは、コンテナ内で機能するはずです。

プロキシを構築する

Nginxリバースプロキシ:

Dockerfile

FROM nginx

RUN rm /etc/nginx/conf.d/*

カスタマイズするため、すべての仮想ホスト設定をリセットします。

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web


services:
  nginx:
    container_name: nginx-proxy
    build: .
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d/:/etc/nginx/conf.d/:ro
      - ./sites/:/var/www/
    networks:
      default: {}
      mynetwork:
        aliases:
          - nginx-proxy

プロキシを実行する

トラスティを使用してプロキシを起動します

docker-compose up -d

問題がないと仮定すると、名前を使用して互いに通信できる2つのコンテナが実行されます。テストしてみましょう。

docker exec -it nginx-proxy bash
ping sample-site
ping nginx-proxy

仮想ホストをセットアップする

最後の詳細は、仮想ホスティングファイルをセットアップして、プロキシを設定する方法に基づいてプロキシがトラフィックを転送できるようにすることです。

仮想ホスティング設定のsample-site.conf:

  server {
    listen 80;
    listen [::]:80;

    server_name my.domain.com;

    location / {
      proxy_pass http://sample-site;
    }

  }

プロキシの設定方法に基づいて、conf.dファイルのvolumes宣言を介してマウントしたローカルのdocker-composeフォルダーにこのファイルを保存する必要があります。

最後になりましたが、nginxに設定をリロードするように伝えてください。

docker exec nginx-proxy service nginx reload

これらの一連のステップは、私が経験したことのほとんどがApacheであったため、かつてないほどの痛烈な502 Bad Gatewayエラーに苦労し、ngginxを初めて学習したときの頭痛の数時間の頂点です。

この回答は、コンテナが相互に通信できないことから生じる502 Bad Gatewayエラーを強制終了する方法を示すことです。

コンテナが互いに話し合うことは、私が明らかなユースケースであると予想していたにもかかわらず、何らかの理由で理解するのが本当に困難だったので、この答えが誰かの苦痛を救うことを願っています。しかし、再び、私は愚かです。そして、このアプローチを改善する方法を教えてください。

10
gdbj

docker links を使用すると、上流のコンテナをnginxコンテナにリンクできます。追加された機能は、dockerがホストファイルを管理することです。つまり、潜在的にランダムなIPではなく名前を使用して、リンクされたコンテナーを参照できます。

9
T0xicCode

AJBの「オプションB」 は、ベースのUbuntuイメージを使用して、自分でnginxをセットアップすることで機能するようにできます。 (Docker HubのNginxイメージを使用した場合は機能しませんでした。)

私が使用したDockerファイルは次のとおりです。

FROM ubuntu
RUN apt-get update && apt-get install -y nginx
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log
RUN rm -rf /etc/nginx/sites-enabled/default
EXPOSE 80 443
COPY conf/mysite.com /etc/nginx/sites-enabled/mysite.com
CMD ["nginx", "-g", "daemon off;"]

私のnginx設定(別名:conf/mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

    location / {
        proxy_pass http://website;
    }
}

upstream website {
    server website:3000;
}

そして最後に、どのようにコンテナを開始するか:

$ docker run -dP --name website website
$ docker run -dP --name nginx --link website:website nginx

これにより、nginxがポート3000を公開する2番目のdockerコンテナーを指すようになりました。

7
Wes Todd

@gdbjの答えは素晴らしい説明であり、最新の答えです。ただし、こちらはより簡単なアプローチです。

したがって、80をリ​​ッスンしているnginxからのすべてのトラフィックを8080を公開する別のコンテナにリダイレクトする場合、最小構成は次のようになります:

nginx.conf:

server {
    listen 80;

    location / {
        proxy_pass http://client:8080; # this one here
        proxy_redirect off;
    }

}

docker-compose.yml

version: "2"
services:
  entrypoint:
    image: some-image-with-nginx
    ports:
      - "80:80"
    links:
      - client  # will use this one here

  client:
    image: some-image-with-api
    ports:
      - "8080:8080"

Dockerドキュメント

6
Diolor

Anand Mani Sankarから 記事 を見つけたところ、docker composerでnginxアップストリームプロキシを使用する簡単な方法を示しています。

基本的に、docker-composeファイルでインスタンスのリンクとポートを設定し、それに応じてnginx.confでアップストリームを更新する必要があります。

2
lsborg