Intro
這篇文章紀錄 Docker For Rails Developer
我認為比較值得注意的內容
我認為這本書比較適合剛接觸 Docker,已經有開發 Rails application 的經驗,嘗試把 Rails server 容器化的工程師看
雖然大多數的概念都是從基礎教起,儘管如此,裡面有提到一些細節還滿實用的,對我來說第一次知道有 docker-machine
這工具可以用,看起來滿方便的
我沒有根據書的順序整理,而是把覺得重要的地方分成寫 Dockerfile 需要注意的細節 / development / production 環境分別需要注意的事情來做分類
Outline
- Intro
- Details when using Docker
- Create Development environment in container
- Run production environment
Details when using Docker
CMD instruction in Dockerfile
Dockerfile 裡面的 instruction 很多有兩種形式: Exec
form 跟 Shell
form
1 | CMD ["bin/rails", "s", "-b", "0.0.0.0"] |
這種表示方法是 Exec
form
如果這樣使用,rails server 會是這個 container 裡面的第一個 process(PID 1),可以確保他正確的接收 unix signals,是比較建議的用法
1 | CMD bin/rails s -b 0.0.0.0 |
這種 form 則是 Shell
form,Docker 會用 /bin/sh -c
去執行這些指令,所以他會這樣執行:/bin/sh -c bin/rails s -b 0.0.0.0
,這樣一來第一個 process 就不是 rails server 而是 /bin/sh
因為 /bin/sh
不會對他的 subproces 傳訊號,所以要關掉 server 的時候可能會導致一些問題,一般來說比較建議 Exec
form
dockerignore file
可以用 .dockerignore
這個檔案避免一些機密檔案被放到 image 裡面
以下是通常會放進去的檔案
1 | #.dockerignore |
Cache
當 Dockerfile 其中的步驟改了,這一層 layer 的 cache 就會被 invalidate
另外對於 COPY
這個指令,只要裡面包含的檔案有改過,cache 也會被 invalidate
然後 image 的每個 layer 都是根據前一層 layer build 起來,因此越前面的步驟改了,後面的 layer 都要重新 build
儘管如此,不注意 cache 的時候還是有可能產生問題,可以看看下面的例子
1 | RUN apt-get update -yqq |
如果我們想要裝最新的 package,但因為前面 update 的指令沒有變,所以已經裝的 package 都是當時做新版的 package,這通常不是我們要的
所以 update 跟 install 最好都寫在一起
1 | RUN apt-get update -yqq && RUN apt-get install -yqq --no-install-recommends \ |
另外因為前面提到的關係,我們不想要就算只改了 readme,整個 image 也要重新 build
像是下面這樣
1 | FROM ruby:2.6 |
這時候可以考慮只把 Gemfile 先 copy 過去
1 | FROM ruby:2.6 |
Create Development environment in container
Run rails server
一般在開發環境,我們會這樣跑起 Rails server:
1 | # 預設跑起來會聽 localhost 的 3000 port |
但如果在 container 裡面跑起 server,對於 container 來說來自 host 的 request 是外面的 request 而不是 localhost,所以需要 bind 在 0.0.0.0
這個 IP 上面,表示會聽所有的 IPv4 ip
1 | > docker run -p 3000:3000 <image_id> bin/rails s -b 0.0.0.0 |
Advanced gem management
Bundler 跟 Docker 其實想要嘗試做到類似的事情,但現在的機制讓他們有點尷尬的沒辦法做到原本想要做的
Bundler 原本的機制是想要安裝還沒安裝的 gem,但因為現在 container 每次都是全新的環境,所以只要 Gemfile 有小變動每次都要裝全部的 gem, 因為他會清掉後面的 cache
1 | COPY Gemfile* /usr/src/app |
如果想要更快的跑起來,而且通常是開發環境才可以考慮這個解法: 把 gem cache 在 mount volume
1 | COPY Gemfile* /usr/src/app |
1 | version: '3' |
這邊做出一個名為 gem_cache 的 volume,把他 mount 在 container 上,每次要跑 docker-compose 之前先執行
docker-compose exec web bundle install
去更新這個 volume 裡面的 gem,接著就可以正常跑起 server
這樣做的缺點是如果 gem 的內容有變動,需要自己注意,否則可能會少裝了什麼套件
Rails server cant start normally
如果我們把 host 的 tmp/pids
mount 在本地的 volume 上面,有時候 app 沒有正常關閉可能會遺留 server.pid 這個檔案,他的路徑是 tmp/pids/server.pid
這時候可以考慮寫在 entrypoint 裡面去解決
1 | ENTRYPOINT ["./docker_entrypoint.sh"] |
1 | # docker-entrypointy.sh |
Run production environment
env files configuration
同時有 production 跟 development 的環境變數,我們的 config 檔可能長這樣:
1 | .env |
1 | #.env/production/web |
SECRET_KEY_BASE 這個環境變數設定 key 用來對 rails 裡面用到的需要加密的資料作加密,其中包括用來加密 cookie,所以如果替換這個環境變數會導致用戶需要重新登入(如果使用 cookie 來做登入機制的話)
Rails 原先預設會把 log 存到 log/<environment>.log
的檔案,設定 RAILS_LOG_TO_STDOUT 讓他 print 到 stdout,這樣可以用 docker logs 去看 log
database 相關設定放在 .env/production/database
1 | #.env/production/database |
Separate dockerfiles
對於 development 跟 production 我們通常會使用不同的 Dockerfile
因為 production 會把 assets 都先 precompile 好,在 development 則是每次 request 都會重新 compile,所以 Dockerfile 會有點不同
1 | # Dockerfile.production |
create production-like environment with VM
docker-machine 是一個 CLI tool,他可以搭配不同的 VM 做出 docker machine
這裡我們使用最常用的 VirtualBox
安裝 docker-machine
1 | > cd /usr/bin |
在使用 docker-machine 做出環境之前要先安裝 VirtualBox,安裝完之後就可以下面步驟
1 | > docker-machine create --driver virtualbox local-vm-1 |
然後我們可以把本地的 docker client 送指令的對象指向 VM 裡面的 docker daemon 而不是 local 的
1 | > eval $(docker-machine env local-vm-1) |
docker-machine 甚至可以配合不同的 cloud provider 做出 instance
1 | > docker-machine create \ |
multi-stage builds
從 Docker 17.05 開始,可以在 Dockerfile 裡面寫不只一個 FROM
每一個 FROM 會開始做新的 stage,然後可以用 COPY 去複製前面一個 stage 裡面的產物
比方說 Rails 就可以複製產生出來的 static files