@peccul is peccu

(love peccu '(emacs lisp cat outdoor bicycle mac linux coffee))

docker-composeとhaproxyでアプリケーションを冗長化する

tl;dr

アプリケーション側のdeploy設定でレプリカ数を指定して、LBとしてhaproxyを置くことで実現した。冗長というよりも負荷分散かもしれない。

  • docker-compose.yml
version: '3.7'
services:
  app:
    ...
    deploy:
      mode: replicated
      replicas: 4
    expose:
      - "8080"
  lb:
    image: haproxy:lts-alpine
    restart: always
    ports:
      - "8080:8080"
    depends_on:
      - app
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
  • haproxy.cfg
defaults
  mode http
  timeout client 10s
  timeout connect 5s
  timeout server 10s 
  timeout http-request 10s

frontend myfrontend
  bind 0.0.0.0:8080
  default_backend myservers

backend myservers
  server-template server 5 app:8080 check resolvers mydns init-addr none

resolvers mydns
  parse-resolv-conf

背景、流れ

minikubeでアプリケーションをスケールさせればよいのだが、kubernetes勉強中にdocker-composeでもできそうな気がして先にそっちをやりたくなった。 (minikube tunnelはうまく動かないし、 kubectl port-forwardをたたくのも手間でその先を調べるのがめんどくさくなった)

今の docker-compose.yml

version: '3.7'
services:
  app:
    ...
    ports:
      - "8080:8080"

まずスケールさせる

この時にアプリケーション側のportsを expose にするのを知る。 スケールさせるとdocker側の待ち受けポートがかぶるので待ち受け側を明示できない

version: '3.7'
services:
  app:
    ...
    deploy:
      mode: replicated
      replicas: 4
    expose:
      - "8080"

haproxyにする

ググってるとnginxの例が出てくるがなんか違う気がしたのと、うまく設定できなかったのでさらに調べる。

以下のドキュメントではhaproxyを使ってて、こっちのほうが適任な気がすると採用。

docker-k8s-lab.readthedocs.io

このドキュメントで使っているイメージはdocker hubに見に行ってもDockerfileが見えなかったので haproxy 本家のコンテナイメージを使うことにした。

hub.docker.com

version: '3.7'
services:
  app:
    ...
    deploy:
      mode: replicated
      replicas: 4
    expose:
      - "8080"
  lb:
    image: haproxy:lts-alpine
    restart: always
    ports:
      - "8080:8080"
    depends_on:
      - app
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg

haproxyの設定ファイル

設定ファイルの説明はこの記事がわかりやすかった。

www.haproxy.com

ここを見ながらアプリケーションを列挙する標準形を作る

www.haproxy.com

docker埋め込みDNSがサービス名で名前解決してくれるのでバックエンドのサーバは app

defaults
  mode http
  timeout client 10s
  timeout connect 5s
  timeout server 10s 
  timeout http-request 10s

frontend myfrontend
  bind 0.0.0.0:8080
  default_backend myservers

backend myservers
  server server1 app:8080
  server server2 app:8080
  server server3 app:8080
  server server4 app:8080

スケールさせるとサーバ数だけ列挙するのが嫌なのと、docker-composeでスケールさせると1つの名前で docker 埋め込みDNSが複数のIPアドレスを返してくれるのでうまいこと活用したかった。

以下の記事でserver-templateが使えることがわかる。スケールさせる上限の数を書いておけば、名前解決でIPが返ってきた数だけサーバを増やしてくれる。

www.haproxy.com

backend myservers
  server-template server 5 app:8080 check resolvers mydns init-addr none

resolvers mydns
  parse-resolv-conf

docker埋め込みDNSサーバは /etc/resolv.conf に記載されてるので、haproxyに指定するネームサーバは明示せず、resolv.confを読み込む指定を使う parse-resolv-conf を指定した。

serverfault.com

まとめ

これでだいたい求めていた構成になった。

  • docker-compose.yml
version: '3.7'
services:
  app:
    ...
    deploy:
      mode: replicated
      replicas: 4
    expose:
      - "8080"
  lb:
    image: haproxy:lts-alpine
    restart: always
    ports:
      - "8080:8080"
    depends_on:
      - app
    volumes:
      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
  • haproxy.cfg
defaults
  mode http
  timeout client 10s
  timeout connect 5s
  timeout server 10s 
  timeout http-request 10s

frontend myfrontend
  bind 0.0.0.0:8080
  default_backend myservers

backend myservers
  server-template server 5 app:8080 check resolvers mydns init-addr none

resolvers mydns
  parse-resolv-conf