如果今天要部署自己的 Shiny App 放在網路上給大家使用,而使用者不需要額外安裝任何 R core 或 Package, 佛心公司 RStudio 有提供免費的 Shiny Server Open Source,只要在 Linux 系統上搭建一個 Shiny Server 就可以了(可以參考這篇)。或是使用 shinyapps.io 這類的網站部署你的 App。
但 Shiny Server Open Source 和免費版的 shinyapps.io 的功能有許多限制,例如僅提供部署 Shiny App,無法做到使用者管控,且單執行緒的資源限制難以提供大量使用者使用等等。
要解決上述問題,你可以選擇購買 Shiny Server Pro,採用每年授權方式計價。然而,比利時有一間佛心公司叫做 Open Analytics ,開發了一個免費且完全開源的 Shiny App 伺服器框架 : ShinyProxy,雖然無法完全取代 Shiny Server Pro ,但幾乎涵蓋了 Shiny Server Open Source 的所有功能,並且提供更多的管控機制。如果今天非企業用戶想要自己架設 Shiny App 的伺服器,ShinyProxy 是一個不錯的替代方案。
What is ShinyProxy?
這邊先簡單介紹一下 ShinyProxy,並與 Shiny Server Open Source 和 Shiny Server Pro 簡單比較一下優劣勢。
ShinyProxy 用到的所有技術和框架都是 100% 開源的 :
ShinyProxy 使用 NGINX 建立網頁伺服器以及使用者管控,並透過 Spring 和 Java 管理 Docker Container ,而各個網頁 App 則是透過 Docker Container 執行,所以執行的網頁 App 並不限於 Shiny,Django、Flask、Vue.js 等都可以透過 ShinyProxy 做管理。
然而,ShinyProxy並沒辦法完全取代 Shiny Server Pro 的所有功能,舉一個我遇到的狀況為例 :
假如今天我的 Shiny App 裡面,需要取得登入 Shiny 伺服器的使用者名稱(username),使用 Shiny Server Pro 的話,只要在 Shiny App 裡使用 session$user
,就可以取得使用者名稱了。
而使用 ShinyProxy 伺服器的話,ShinyProxy 會把登入 NGINX 的使用者名稱,做為系統變數丟到 Docker Container中,因此可以在 Shiny App 裡面使用
Sys.getenv("SHINYPROXY_USERNAME")
來取得登入至 ShinyProxy 伺服器的使用者名稱,但由於每個使用者登入至同一個 Shiny App 時,都會使用不同的 container 環境,所以多個使用者無法在同一個 session 中互動。要讓多個使用者在同一個 session 中互動的話,當然也可以在 ShinyProxy 的 config 設定中,開放多個使用者連線至同一個 container,但就無法個別取得使用者名稱,因為環境變數Sys.getenv
取得的環境變數都會是相同的。
另外 Shiny Server Pro 跟 Rstudio 有很好的整合(畢竟是同一家公司的產品),可以透過 Rstudio Connect 快速的部署你的 Shiny App 到 Shiny Server 上,而使用 ShinyProxy 部署網頁 App,必須要寫 Dockerfile,把 App 環境打包成 Docker Image,並在 ShinyProxy Config 中做設定,稍嫌複雜了點。
How to install ShinyProxy?
講這麼多,總算要開始實做了,以下環境皆使用 Ubuntu 18.04 示範,當然 ShinyProxy 也可以在 Windows 系統上執行,但管理跟使用就沒那麼方便,安裝過程也不同。
Windows 系統的 Shiny Proxy 是直接用 Java 去執行 jar 檔來運作的,而在 Linux 上則是安裝好後透過 systemctl 來執行跟管理,相對上比較直覺一些。
以下的安裝方式是參考官網教學,然後把一些我踩過的坑給填上,想要看原版的讀者可以回 ShinyProxy 官網去看。
Step 1 : Install Java
首先安裝 Java 是一定要的,這邊安裝 Java 11 的版本,但官網說只要是 Java 8 以上都可以,安裝好後可以透過 java -version
來確認 Java 是否有正確安裝。
Step 2 : Install Docker
安裝 Docker 時,要注意一下 CPU 架構,我這邊是安裝 AMD64 的版本。安裝好後可以透過 sudo docker run hello-world
來確認 Docker 是否有正確安裝。
Step 3 : Setting docker start options
為了建立 Docker Socket 和 Spring 的溝通方式,我們需要在 Docker 執行時開一個 port (預設為 2375) 給 Spring,所以需要在 config 檔案中設定一下。
首先在指定位置下新增一個 docker.service.d
資料夾,並在該資料夾下新增一個 override.conf
檔案:
之後在override.conf
中新增下面的文字,之後用 ESC → :wq
儲存並退出 (vim 編輯器的指令):
修改完後要記得重啟 Docker 服務 :
Step 4 : Install & Run ShinyProxy
首先安裝一個官方提供的 docker image,裡面包含兩個示範用的 Shiny App :
sudo docker pull openanalytics/shinyproxy-demo
這邊安裝時最新的版本號是 2.3.0
安裝完後要新增一個 configuration file,預設路徑為 /etc/shinyproxy/application.yml
至於Configuration file怎麼寫,官網有給詳細的說明,一個簡單的範例如下 :
簡單介紹一下比較可能會用到的參數 :
- port :
Shinyproxy 伺服器的 port - authentication :
這邊要設定成 simple,才能吃到下面自訂的使用者帳號和密碼 (官網範例預設為ldap) - users :
這邊是自訂使用者的帳號和密碼,groups可以把使用者分組,讓你的 Shiny App 可以針對不同的組別開放顯示。 - docker :
docker 的設定值,這邊保持預設即可 - specs :
最重要的參數,這邊定義了你的 ShinyProxy 包含哪些 Shiny App,這邊也可以設定一些 docker 的參數,會直接影響 Shiny App 的 docker container 如何啟動 :
-id
: Shiny App 的顯示名稱
-container-cmd
: docker container 執行後,要啟動 Shiny App 的指令
-container-image
: 選擇要啟動 docker 中的哪個 docker image
-acess-groups
: 哪些 user groups 才有權限使用這個 Shiny App
-container-network
: host代表所有的 user 都會使用同一個 Shiny session,預設為bridge
,表示每個使用者會各自開一個 container,Shiny session 就不會共用了。如果設定為 host,需搭配應一個參數internal-networking : true
使用。( docker 的參數)
-container-volumes
: 可以掛載 host 的檔案或資料夾至 container 中,這樣多個 user 也可以共用相同的檔案。等同於docker run --volume
參數修改完後,必須要建立一個 log 檔位置,並修改權限讓 ShinyProxy 可以讀寫 :
設定完後記得重啟 ShinyProxy 服務 :
sudo systemctl restart shinyproxy
重新啟動 ShinyProxy 服務,然後登入瀏覽器 http://localhost:8080應該可以看到如下畫面,輸入帳號密碼登入後就可以看到官方範例提供的兩個 Shiny App了!
Deploy your own shiny apps
要 Deploy 自己的 Shiny App 到 ShinyProxy ,必須要建立 Docker Image 提供給 ShinyProxy ,我自己有整理出來兩種做法,For 不同面向的 Shiny App使用 :
- 把 Shiny App 的 Source Code (
server.R
和ui.R
)都打包進 Docker Image, 然後在 Configuration File 內,不同的 Shiny App,就在container-image
參數提供不同的 Docker Image。
此法的優點就是每個使用者都是各自使用獨立的環境,也可以各自讀寫檔案互不衝突。但缺點是每次更新 Shiny App 的程式碼時都要重新 Build Docker Image。 - 不把 Shiny App 的 Source Code 打包進 Docker Image,然後在 Configuration File 檔內,用參數
container-volumns
把 Shiny App 的資料夾掛載進去 Docker Container 內。
此法的優點是要對 Shiny App 做任何維護或修改時,不需要重 Build Docker Image,因為 Container 會直接吃到 Local 端修改過的程式碼。
但是如果使用者讀寫檔案的話,因為是 Local 端的相同路徑,所以讀寫都會吃到同一個檔案,且如果有用到新的 R package 的話,還是要重新安裝 Package 並重新 Build Docker Image。
假設我的server.R
和 ui.R
分別放在叫做 MyApp 的資料夾內。
第一步要先安裝包含 R 核心的 Docker Image,佛心公司 OpenAnalytics 有提供最新版的在 DockerHub 上。
sudo docker pull openanalytics/r-base
以第一種方法為例,Dockerfile 可以這樣寫 :
- Part 1 : 以
openanalytics/r-base
這個 Docker Image 為 Base 來建立我們的 Docker Image - Part 2 : 需要安裝的 Linux 系統套件,有些 R package 需要搭配系統套件才可安裝
- Part 3 : Shiny App 會用到的 R package 都需要在此步驟安裝
- Part 4 : 把 Local 端的 Shiny App 資料夾整個複製到 Docker Image 內,就是因為這步把 Shiny App 的程式碼整組搬進去,所以每次修改程式碼就要重新 Build Docker Image。
如果要使用第二種方法,掛載本地端的資料夾,則這個參數可以留白。 - Part 5 : 把
Rprofile.site
也搬進 Docker Image 內,這個檔案是為了確保 Shiny App 會在正確的 Port (3838) 上執行。Rprofile.site
檔內長這樣
local({
options(shiny.port = 3838, shiny.host = “0.0.0.0”)
})
- Part 6 : 這邊指定的 Port 要與上面的
Rpofile.site
檔內指定的 Port 相同,這樣才可以從外部 Access 到 Docker Container 的 Port - Part 7 : 要執行 Shiny App 的指令,與 Configuration File 內的
container-cmd
基本上相同,如果 Dockerfile 內有寫了,則在 configuration file 內不加上container-cmd
參數也沒關係。因為 Container 一啟動就會執行 Part 7 的指令。
要 Build Docker Image,就在 Dockerfile 的路徑下執行 :
sudo docker build -t shiny/MyApp .
其中 shiny/MyApp
是自己定義的 Docker Image 名稱,要注意最後面一定要加一個點點 .
,代表 Dockerfile 在本地路徑下,當初 Build 好久一直出錯,後來才發現一個點都不能少, .
是 Build Docker Image 指令中神聖且不可分割的一部份。
要記得任何 Shiny App 或 Docker Image 的更動都要 sudo systemctl restart shinyproxy
才會生效。
其實當初會發現這個東西,是用在我自己用 Shiny 寫的多人聊天室,讓某些資訊封閉不能帶手機的公司裡面的親友們可以對外溝通。
但當每人都懶得換使用者名稱的時候,就會變成不知道訊息是誰送出的,這時候如果有一個帳號的管理機制,可以讓 Shiny App 抓到登入的使用者名稱,就可以解決這個問題,但 Shiny Server Pro 授權費也不便宜,才找到 ShinyProxy 這個好用的替代品。
這篇把安裝跟 Deploy 過程中一路踩過的坑整理起來,怕自己以後忘記。反正 Medium 上面的中文文章好像也沒什麼人在看,加個小彩蛋應該不會有人發現吧。