Linux中國

Docker Compose:搭建開發環境的好方式

大家好!我又寫了一篇關於 我最喜歡的電腦工具 的文章。這一篇講的是 Docker Compose!

本文主要就是講一講我對 Docker Compose 有多麼滿意啦(不討論它的缺點)!咳咳,因為它總能夠完成它該做的,並且似乎總能有效,更棒的是,它的使用還非常簡單。另外,在本文中,我只討論我是如何用 Docker Compose 來搭建開發環境的,而不涉及它在生產中的使用。

最近,我考慮了很多關於這種個人開發環境的搭建方式,原因是,我現在把所有的計算工作都搬到了一個私有雲上,大概 20 美元/月的樣子。這樣一來,我就不用在工作的時候花時間去思考應該如何管理幾千台 AWS 伺服器了。

在此之前,我曾花了兩天的時間,嘗試使用其他的工具來嘗試搭建一個開發環境,搭到後面,我實在是心累了。相比起來,Docker Compose 就簡單易用多了,我非常滿意。於是,我和妹妹分享了我的 docker-compose 使用經歷,她略顯驚訝:「是吧!你也覺得 Docker Compose 真棒對吧!」 嗯,我覺得我應該寫一篇博文把過程記錄下來,於是就有了你們看到的這篇文章。

我們的目標是:搭建一個開發環境

目前,我正在編寫一個 Ruby on Rails 服務(它是一個計算機「調試」遊戲的後端)。在我的生產伺服器上,我安裝了:

  • 一個 Nginx 伺服器
  • 一個 Rails 服務
  • 一個 Go 服務(使用了 gotty 來代理一些 SSH 連接)
  • 一個 Postgres 資料庫

在本地搭建 Rails 服務非常簡單,用不著容器(我只需要安裝 Postgres 和 Ruby 就行了,小菜一碟)。但是,我還想要把匹配 /proxy/* 的請求的發送到 Go 服務,其他所有請求都發送到 Rails 服務,所以需要藉助 Nginx。問題來了,在筆記本電腦上安裝 Nginx 對我來說太麻煩了。

是時候使用 docker-compose 了!

docker-compose 允許你運行一組 Docker 容器

基本上,Docker Compose 的作用就是允許你運行一組可以互相通信 Docker 容器。

你可以在一個叫做 docker-compose.yml 的文件中,配置你所有的容器。我在下方將貼上我為這個服務編寫的 docker-compose.yml 文件(完整內容),因為我覺得它真的很簡潔、直接!

version: "3.3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password # yes I set the password to 'password'
  go_server:
    # todo: use a smaller image at some point, we don't need all of ubuntu to run a static go binary
    image: ubuntu
    command: /app/go_proxy/server
    volumes:
      - .:/app
  rails_server:
    build: docker/rails
    command: bash -c "rm -f tmp/pids/server.pid && source secrets.sh && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
  web:
    build: docker/nginx
    ports:
      - "8777:80" # this exposes port 8777 on my laptop

這個配置包含了兩種容器。對於前面兩個容器,我直接使用了現有的鏡像(image: postgresimage: ubuntu)。對於後面兩個容器,我不得不構建一個自定義容器鏡像,其中, build: docker/rails 的作用就是告訴 Docker Compose,它應該使用 docker/rails/Dockerfile 來構建一個自定義容器。

我需要允許我的 Rails 服務訪問一些 API 密鑰和其他東西,因此,我使用了 source secrets.sh,它的作用就是在環境變數中預設一組密鑰。

如何啟動所有服務:先 「build」 後 「up」

我一直都是先運行 docker-compose build 來構建容器,然後再運行 docker-compose up 把所有服務啟動起來。

你可以在 yaml 文件中設置 depends_on,從而進行更多啟動容器的控制。不過,對於我的這些服務而言,啟動順序並不重要,所以我沒有設置它。

網路互通也非常簡單

容器之間的互通也是一件很重要的事情。Docker Compose 讓這件事變得超級簡單!假設我有一個 Rails 服務正在名為 rails_server 的容器中運行,埠是 3000,那麼我就可以通過 http://rails_server:3000 來訪問該服務。就是這麼簡單!

以下代碼片段截取自我的 Nginx 配置文件,它是根據我的使用需求配置的(我刪除了許多 proxy_set_headers 行,讓它看起來更清楚):

location ~ /proxy.* {
  proxy_pass http://go_server:8080;
}
location @app {
  proxy_pass http://rails_server:3000;
}

或者,你可以參考如下代碼片段,它截取自我的 Rails 項目的資料庫配置,我在其中使用了資料庫容器的名稱(db):

development:
  <<: *default
  database: myproject_development
  host: db # <-------- 它會被「神奇地」解析為資料庫容器的 IP 地址
  username: postgres
  password: password

至於 rails_server 究竟是如何被解析成一個 IP 地址的,我還真有點兒好奇。貌似是 Docker 在我的計算機上運行了一個 DNS 服務來解析這些名字。下面是一些 DNS 查詢記錄,我們可以看到,每個容器都有它自己的 IP 地址:

$ dig +short @127.0.0.11 rails_server
172.18.0.2
$ dig +short @127.0.0.11 db
172.18.0.3
$ dig +short @127.0.0.11 web
172.18.0.4
$ dig +short @127.0.0.11 go_server
172.18.0.5

是誰在運行這個 DNS 服務?

我(稍微)研究了一下這個 DNS 服務是怎麼搭建起來的。

以下所有命令都是在容器外執行的,因為我沒有在容器里安裝很多網路工具。

第一步::使用 ps aux | grep puma,獲取 Rails 服務的進程 ID。

找到了,它是 1837916!簡單~

第二步::找到和 1837916 運行在同一個網路命名空間的 UDP 服務。

我使用了 nsenter 來在 puma 進程的網路命令空間內運行 netstat(理論上,我猜想你也可以使用 netstat -tupn 來只顯示 UDP 服務,但此時,我的手指頭只習慣於打出 netstat -tulpn)。

$ sudo nsenter -n -t 1837916 netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:32847        0.0.0.0:*               LISTEN      1333/dockerd
tcp        0      0 0.0.0.0:3000            0.0.0.0:*               LISTEN      1837916/puma 4.3.7
udp        0      0 127.0.0.11:59426        0.0.0.0:*                           1333/dockerd

我們可以看到,此時有一個運行在 59426 埠的 UDP 服務,它是由 dockerd 運行的!或許它就是我們要找的 DNS 服務?

第三步:確定它是不是我們要找的 DNS 服務

我們可以使用 dig 工具來向它發送一個 DNS 查詢:

$ sudo nsenter -n -t 1837916 dig +short @127.0.0.11 59426 rails_server
172.18.0.2

奇怪,我們之前運行 dig 的時候,DNS 查詢怎麼沒有發送到 59426 埠,而是發送到了 53 埠呢?這到底是怎麼回事呀?

第四步:iptables

對於類似「這個服務似乎正運行在 X 埠上,但我卻在 Y 埠上訪問到了它,這是什麼回事呢?」的問題,我的第一念頭都是「一定是 iptables 在作怪」。

於是,我在運行了容器的網路命令空間內執行 iptables-save,果不其然,真相大白:

$ sudo nsenter -n -t 1837916 iptables-save
.... redacted a bunch of output ....
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 59426 -j SNAT --to-source :53
COMMIT

在輸出中有一條 iptables 規則,它將 53 埠的流量發送到了 59426 上。哈哈,真有意思!

資料庫文件儲存在一個臨時目錄中

這樣做有一個好處:我可以直接掛載 Postgres 容器的數據目錄 ./tmp/db,而無需在我的筆記本電腦上管理 Postgres 環境。

我很喜歡這種方式,因為我真的不想在筆記本電腦上獨自管理一個 Postgres 環境(我也真的不知道該如何配置 Postgres)。另外,出於習慣,我更喜歡讓開發環境的資料庫和代碼放在同一個目錄下。

僅需一行命令,我就可以訪問 Rails 控制台

管理 Ruby 的版本總是有點棘手,並且,即使我暫時搞定了它,我也總是有點擔心自己會把 Ruby 環境搞壞,然後就要修它個十年(誇張)。

(使用 Docker Compose)搭建好這個開發環境後,如果我需要訪問 Rails 控制台 console (一個互動式環境,載入了所有我的 Rails 代碼),我只需要運行一行代碼即可:

$ docker-compose exec rails_server rails console
Running via Spring preloader in process 597
Loading development environment (Rails 6.0.3.4)
irb(main):001:0>

好耶!

小問題:Rails 控制台的歷史記錄丟失了

我碰到了一個問題:Rails 控制台的歷史記錄丟失了,因為我一直在不斷地重啟它。

不過,我也找到了一個相當簡單的解決方案(嘿嘿):我往容器中添加了一個 /root/.irbrc 文件,它能夠把 IRB 歷史記錄文件的保存位置指向一個不受容器重啟影響的地方。只需要一行代碼就夠啦:

IRB.conf[:HISTORY_FILE] = "/app/tmp/irb_history"

我還是不知道它在生產環境的表現如何

到目前為止,這個項目的生產環境搭建進度,還停留在「我製作了一個 DigitalOcean droplet(LCCT 譯註:一種 Linux 虛擬機服務),並手工編輯了很多文件」的階段。

嗯……我相信以後會在生產環境中使用 docker-compose 來運行一下它的。我猜它能夠正常工作,因為這個服務很可能最多只有兩個用戶在使用,並且,如果我願意,我可以容忍它在部署過程中有 60 秒的不可用時間。不過話又說回來,出錯的往往是我想不到的地方。

推特網友提供了一些在生產中使用 docker-compose 的注意事項:

  • docker-compose up 只會重啟那些需要重啟的容器,這會讓重啟速度更快。
  • 有一個 Bash 小腳本 wait-for-it,你可以用它來保持等待一個容器,直到另一個容器的服務可用。
  • 你可以準備兩份 docker-compose.yaml 文件:用於開發環境的 docker-compose.yaml 和用於生產環境的 docker-compose-prod.yaml。我想我會在分別為 Nginx 指定不同的埠:開發時使用 8999,生產中使用 80
  • 人們似乎一致認為,如果你的項目是一台計算機上運行的小網站,那麼 docker-compose 在生產中不會有問題。
  • 有個人建議說,如果願意在生產環境搭建複雜那麼一丟丟,Docker Swarm 就或許會是更好的選擇,不過我還沒試過(當然,如果要這麼說的話,幹嘛不用 Kubernetes 呢?Docker Compose 的意義就是它超級簡單,而 Kubernetes 肯定不簡單 : ))。

Docker 似乎還有一個特性,它能夠 把你用 docker-compose 搭建的環境,自動推送到彈性容器服務(ESC)上,聽上去好酷的樣子,但是我還沒有試過。

docker-compose 會有不適用的場景嗎

我聽說 docker-compose 在以下場景的表現較差:

  • 當你有很多微服務的時候(還是自己搭建比較好)
  • 當你嘗試從一個很大的資料庫中導入數據時(就像把幾百 G 的數據存到每個人的筆記本電腦里一樣)
  • 當你在 Mac 電腦上運行 Docker 時。我聽說 Docker 在 macOS 上比在 Linux 上要慢很多(我猜想是因為它需要做額外的虛擬化)。我沒有 Mac 電腦,所以我還沒有碰到這個問題。

以上就是全部內容啦!

在此之前,我曾花了一整天時間,嘗試使用 Puppet 來配置 Vagrant 虛擬機,然後在這個虛擬機里配置開發環境。結果,我發現虛擬機啟動起來實在是有點慢啊,還有就是,我也不喜歡編寫 Puppet 配置(哈哈,沒想到吧)。

幸好,我嘗試了 Docker Compose,它真好簡單,馬上就可以開始工作啦!

via: https://jvns.ca/blog/2021/01/04/docker-compose-is-nice/

作者:Julia Evans 選題:lujun9972 譯者:lkxed 校對:turbokernel

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國