最近(其實就是今天阿阿)在寫 webapp 又遭遇到了困難,是這樣地:
我用 yeoman host 了 project, 終於到了要 deploy 的這一刻惹。
這是候我們要先 grunt build
一下(把圖的 sprite 壓縮阿,code 壓縮,css 壓縮什麼的),然後再把這些東西丟上去 server 就搞定。但是
我不想把 build 出來的 code 也放進 version control 裡面,因為我覺得這種 generate 出來的東西沒有什麼邏輯在裡面,再者長久下來,這些東西會讓整個 repository 變得很肥,clone 一下就要好久。
以下是我 deploy 的設定:
- Capistrano (是的,我還是用 ruby base 的 capistrano 在 deploy...因為我對 grunt 實在是沒有愛)
- github
大致上的流程就是,
- 寫好 code 並 push 到 github 上面
- 在 local 下指令 deploy
這時候呢,Capistrano 就會 ssh 到 server, 然後從 server 上把 github 的 code 拉下來,然後再在 server 上做任何你叫他做的事情 (像是重開 worker 阿什麼的) 然後就可以用了。
回到這篇文章的主題,就是 我不想把 build 出來的 code 也放進 version control 裡面, 所以呢,在 server 上把 code 拉下來的時候就會沒有 build 好的版本。這時候要嘛
在 local build 好然後再叫 Capistrano 傳到 server 上: 這個作法我覺得相當可怕,因為變成真正在 server 上 run 的 code 是沒有任何版本的資訊的,意思是說有可能我們現在
master
上面是 commit-a, 但是真正在 server 上跑的版本可能是 commit-xyz。到時候如果要 rollback 或者有什麼問題要 trace 的時候有可能會欲哭無淚。在 server 上把
grunt
,bower
等等開發的環境裝起來,然後在 server 上直接 build: 這個方法好一點,但又會遇到另一個問題就是 node 的版本跳得很快,所以如果 server 上有一個以上的 project 的話,有可能會有 node 的版本撞到的情形。(這樣想起來好像可以直接用 nvm 之類的解決,可是用 docker 就是比較潮阿)
好反正我就用了 docker, 我的目標是:
- build 出來的 code 不要進 version control, 但本身要有版本對應的資訊
- 在 server 上
npm install
,bower install
要快,之前的 deployment 如果有裝過同樣的東西就不要再重裝 - 在 server 上不要裝 development environment (像是 bower, node, grunt) 等
解法:
所以我搞了一個有node, grunt, bower, compass 的 docker image,然後在每次的 deployment 的時候,在 server 上用這個 image run 一個 container 來 build
。在 build 的時候,把 project 的目錄 link 到 container 裡面這樣。
所以呢,以 npm install
來說:
docker run -t --rm \
-v $(pwd):/app \
-v $(pwd)/node_modules:/app/node_modules \
-w /app \
yeoman-builder:latest \
npm install
-
bower install
:
docker run -t --rm \
-v $(pwd):/app \
-v $(pwd)/bower_components:/app/bower_components \
-w /app yeoman-builder:latest \
bower --allow-root --config.interactive=false install
-
grunt build
:
docker run -t --rm \
-v $(pwd):/app \
-v $(pwd)/node_modules:/app/node_modules \
-v $(pwd)/bower_components:/app/bower_components \
-w /app yeoman-builder:latest \
grunt build
這樣子就大功告成惹!
小陷阱
我在 AWS EC2 的 micro instance 上面嘗試從 docker hub 上 pull image 上來結果竟然失敗… 但直接把
Dockerfile
copy 過去,在 server 上 build image 則沒有問題。我猜有可能是 micro instance 太弱小了所以 memory 之類的被吃完也有可能,但這點我沒有深究。在 run container 的時候,如果有 symbolic link 要特別再 link 一次,不然在 container 裡面會沒辦法 access。以上面的 project 為例,我在
app/
裡面建立了一個 symbolic linkapp/node_modules
link 到上一層的一個資料夾(目的是為了在 deployments 之間共用 node modules), 在 run container 的時候雖然有 linkapp/
(-v $(pwd):/app
) 但還是要再另外 linknode_modules
(-v $(pwd)/node_modules:/app/node_modules
) 才行。
Capistrano settings:
desc 'link `node_modules` and `bower_components` to shared_path'
after :publishing, :link_shared do
on roles(:app) do
execute :ln, '-s', shared_path.join('node_modules'), release_path.join('node_modules')
execute :ln, '-s', shared_path.join('bower_components'), release_path.join('bower_components')
end
end
desc 'build by docker'
after :link_shared, :docker_build do
on roles(:app) do
within release_path do
execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
'-v', '$(pwd)/node_modules:/app/node_modules',
'-w', '/app', 'mz026/yeoman-builder:latest', 'npm', 'install'
execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
'-v', '$(pwd)/bower_components:/app/bower_components',
'-w', '/app', 'mz026/yeoman-builder:latest',
'bower', '--allow-root', '--config.interactive=false', 'install'
execute :docker, 'run', '-t', '--rm', '-v', '$(pwd):/app',
'-v', '$(pwd)/node_modules:/app/node_modules',
'-v', '$(pwd)/bower_components:/app/bower_components',
'-w', '/app', 'mz026/yeoman-builder:latest', 'grunt', 'build'
end
end
end
心得:
Docker hub 可以和 github / bitbucket 連結來做 automated build, 意思是說,我們在 github 上面開一個 repository 裡面放著 Dockerfile
,然後從 Docker hub 設定,這樣一來,只要 github 的這個 project 有改動,Docker hub 就會自己去拉新的 code 下來 build image,有點貼心喔!