Just note



Pack your bags, old lover 收拾你的行李


Cause I've been thinking of you 因為我想起了你


As I fall through the hours 當我倒下(失去你) 的這段時間


And I slip through the day 且讓日子一天一天的滑過


Now the days are not ours 現在已經不屬於我們的日子


I waste away 我浪費了


But if I just keep on singing 但如我我繼續歌唱


Maybe I can sing my heart out 或許可以唱出我的心聲


If I just keep on writing 如果我繼續寫


Maybe I can write you out Of me, me... 或許我可以把你寫離我(的心)


But how long will it be, be Until you leave? 但是要持續多久, 才會讓你(的一切) 離我而去


Butterflies still flutter in my stomach 蝴蝶仍在我的胃裡飛舞


Every time I see your name 每次我看見你的名字


Oh, what a shame 噢, 多麼恥辱


Cause now there's someone here waiting for me 因為現在, 某個人正在這裡等我


And it's hard to explain 而且這很難解釋


That it's you, only you I need 這都是因為你, 只有妳才是我需要的


There's someone waiting for me, me 這裡某個人正在等我


So how long will it be, be Until you leave? 所以要多久才可以讓你離開我(心裏) 

今天一個人, 把明天要 production 負責的部分, 又重新 review 了幾次, try 了幾次,
慵懶的假日, 懶得思考幾乎都用暴力法解決程式上的問題,

我也明白, 貪圖一時的方便, 會增加未來的工作量, 這是一種與未來的借貸儀式, 總覺得充滿恐懼,
這是我人生的第二份工作, 我很在意一些事情, 想要盡快上手, 分攤組員的壓力,
常常懷疑自己真的適合這份工作嗎? 另一方面又希望自己能在30 歲之前, 還清貸款,
有點成就之類的...

在胡思亂想的過程中, 想到今敏大師, 音樂的旋律, 自己在腦海中跑了起來,
重新讀了一次 今敏先生的文字, 心中的壓力, 稍微釋懷了,
希望你在天上一切安好



再見了。

今年的5月18日, 是我忘不了的日子。這一天,武藏野紅十字醫院心臟內科的醫師作出如下的宣告:「你是胰臟癌末期,癌細胞已經轉移至全身各處骨頭,最多只能再活半年。」我跟 內人一起聽到這番話。命運實在太過唐突、太過沒有道理,使我們倆幾乎無法獨力承受。我平常心裡就在想:「隨時都有可能會死掉,這也是沒辦法的。」但這未免 太過突然了。

不過,或許真的可以說是有事先徵兆。2~3個月前,我整片背部各處,以及我的腳跟等部位都出現劇烈疼痛,右腳也使不上力,走路更出現了很大的困難。我有找過針灸師與整脊師,但狀況並未改善。經過MRI(核磁共振)與PET-CT(正子斷層掃描)等等精密儀器檢查的結果,就是剛剛那段「只能再活半年」的宣告。這簡直像是回過神來,死神就站在背後似的,我實在也是束手無策。

宣告後,我與內人一同摸索活下去的辦法。真的是拚了老命。我們得到了可靠的友人以及無比強力的支援。我拒絕抗癌劑,想要相信與世間普遍觀念略略不同的世界觀 活下去。感覺拒絕「普通」這點,倒還挺有我的風格的。反正多數派當中也沒有我的容身之處,即使是醫療方面也一樣。同時這次也讓我體認到,現代醫療的主流派 背後,究竟有著什麼樣的機制。

「就在自己選擇的世界觀當中活下去吧!」可惜,光靠一股氣力是沒有用的,這點跟製作作品時一樣。病情確實一天天的惡化。

同時我也算是一個社會人,因此平常的我也大約接受了一半的世間普遍世界觀。畢竟我也會乖乖的繳納稅金。就算不足以自傲,我也夠資格算是日本社會的成員。 所以在與我「活下去」的世界觀作準備的同時,我也打算著手「替我的死亡作準備」。雖然完全沒有就緒就是了。 準 備之一,就是找來兩個值得信賴的朋友協助,成立一間公司,負責管理今敏微不足道的著作權。另外一項準備就是,寫好遺囑好讓我並不算多的財產能順利地讓內人 繼承。當然了,我死後應該是不會發生遺產爭奪戰,但我也想替獨活在世界上的妻子盡可能除去不安,這樣我才能稍微安心地離開。

各種手續,我與內人都很頭痛的事務處理、事先調查等等,由於厲害的朋友相助,進行得十分迅速。後來我併發肺炎的危急情況當中,意識矇矓地在遺囑上簽下最後的名字時,我心裡總算是覺得:這樣死掉應該也可以了吧。「唉…總算能死了。」畢 竟在兩天前就被救護車送到武藏野紅十字,過了一天又被救護車送到同一間醫院。也因此住院作了詳細檢查。檢查結果是併發了肺炎,肺部也有嚴重積水。我跟醫生 問了個究竟,他的回答倒是挺官腔的。就某方面而言,也挺感謝他的。「頂多只能撐個一兩天…就算熬了過去,最多月底就不行了吧。」聽著聽著我心想:「怎麼講 得跟天氣預報一樣…。」不過事態確實越來越緊急了。那是7月7日的事。這年七夕也未免太殘忍了。

所以我很快地下了決定:我要死在家裡。或 許對我身邊的人而言,最後仍然給他們添了很大的麻煩,好不容易才找到能讓我離開醫院回到家裡的方法。一切都多虧了我妻子的努力,醫院那看似放棄卻又真的有 幫到我的實際協助,外部醫院的莫大支援,以及屢屢令人只能認為是「天賜」的偶然,甚至讓我無法相信現實當中的偶然與必然,竟然能這麼巧合地環環相扣。畢竟 這又不是《東京教父》啊。

在 我妻子替我設法離開醫院奔走時,我則是對醫生說:「就算一天也好、半天也好,只要我留在家裡就一定還有辦法!」說完後我就一個人留在陰暗的病房內等死。當 時很寂寞,但我心裡想的卻是:「死或許也不算壞。」這想法不是出於什麼特別的理由,或許是因為如果不這麼想我就撐不下去了吧,但總之,當時我的心情是連我 自己都非常驚訝的平穩。

只有一點讓我說什麼都無法接受。「我說什麼都不想死在這種地方…。」此時眼前掛在牆壁上的月曆開始晃動,房間看起來越來越大。「傷腦筋…怎麼是從月曆裡跑出 來接我走呢。我的幻覺真是不夠充滿個性。」此時我的職業意識仍然在運作,令我忍不住想笑。但此時或許是我最接近「死亡」的一刻吧。我真正感覺到死亡的逼 近。

在「死亡」與床單的包裹之下,加上許多人的盡力而為,我奇蹟似地逃出了武藏野紅十字,回到自己家中。死也是很痛苦的。我先聲明,我並不是批評或是討厭武藏野紅十字醫院,請各位不要誤會。我只是想要回自己家而已。回到那個我生活的地方。

有一點讓我略為吃驚。就是當我被送到家中客廳時,居然還附帶了臨死體驗中最常聽到的「站在高處看著自己被搬到房間內的模樣」。大概是站在地面上數公尺的地 方,用有點廣角的鏡頭俯瞰著包含著自己的風景。房間中央床鋪的四角形,給了我特別大的印象。被裹在床單內的自己,放在那塊四角形上。感覺並不怎麼小心翼 翼,不過也沒什麼好抱怨的。

我本來應該是在家裡等死的。沒想到。我似乎是輕輕鬆鬆地翻過了肺炎這難關。哎呀?我居然這麼想:「竟然會沒死成啊(笑)。」後來滿腦子都只有「死」的我,覺得有一次似乎真正死掉了。在朦朧的意識深處,「reborn」這個詞彙晃動了數次。不可思議地,第二天起我的氣力再度啟動了。我覺得這一切,都是我妻子、來探我的病分我一份元氣的那些人、來替我加油的朋友、醫師、護士、看護等等所有人的功勞。我打從心裡這麼想。

既然活下去的氣力都再度啟動了,我就不能繼續模模糊糊地下去。我謹記這是多分到的一段壽命,所以我更得好好運用。同時我也想要至少多還一份人情。其實我罹患癌症這件事,我只告訴了身邊極少數的人,連我雙親都不知道。特別是這會替我的工作製造許多麻煩,所以我說也說不出口。

我本來也想上網宣布我得了癌症,每天跟大家報告我剩餘的人生,但因為我擔心今敏即將死亡這事說來雖小,卻也會造成許多影響,也因此非常對不起身邊的親朋好友。真的是非常抱歉。

死前,我還想再見許多人一面,跟他們說幾句話。這段人生當中,我有家人,親戚,從國小國中開始交往的朋友,高中同學,大學認識的同伴,在漫畫的世界當中結識 並交換許多刺激的人們,在動畫的世界中一同工作、一同喝酒、用同樣的作品刺激彼此的技術、同甘共苦的眾多同伴,由於擔任動畫導演得以認識的無數人們,以及 世界各地願意自稱是我的影迷的許多人。還有透過網路認識的朋友。

如果可以,我還想見很多人一面(當然也有不想見到的人)。但是見了面後,感覺我腦子裡「我再也見不到這個人了!」的想法會累積得越來越多,讓我沒有辦法乾脆 地赴死。同時即使略為恢復,我所剩的氣力也不多了,要見別人的面需要莫大的決心。越想見面的人,見到面卻越痛苦,真是太諷刺了。再加上,由於癌細胞轉移到 骨頭上,下半身開始麻痺,我幾乎無法下床。我不想讓別人看到我瘦成皮包骨的模樣。我希望許許多多的朋友記得的能是那個還充滿元氣的今敏。

不知道我病情的親戚、所有朋友、所有認識的人,我要藉這個場合跟你們道歉。但我真的很希望你們可以理解今敏的這份任性。因為今敏本來就是「這樣的傢伙」嘛。 想到你們的臉,我的腦子裡就湧現許多美好的回憶與笑容。真的非常感謝大家給了我這麼棒的回憶。我好愛自己生活的這個世界。這樣的想法,本身就是一種幸福。

在我的人生當中認識的不算少的人們,無論影響是正面或是負面,都是構成「今敏」這個人的必要成分,我要感謝所有的邂逅。雖然結果是我四十幾歲就早逝了,但我 也認為這是無可取代的我的命運。同時我也有過十分多的美好經驗。現在我對於死,只有這個想法:「也只能說遺憾了。」是真的。

雖然我可以把這麼多的虧欠想成是無可奈何的,並且放棄,還是有件事讓我說什麼都過意不去。就是我的雙親,以及MAD HOUSE丸山先生。一方是今敏的親生父母,另一方則是動畫導演方面的再生父母。雖然是有點遲了,除了坦白相告,我也沒有其他方法可選。當時我真的希望獲得原諒。

看到丸山先生來到家裡探望我時,我控制不了我的淚,也控制不了自慚形穢的想法。「對不起,我居然變成這樣…。」丸山先生什麼話也沒說,只是搖搖頭,握住我的 雙手。讓我的心裡充滿了感激。能夠跟這位先生一起工作的感激之情,化為無法訴諸言語的歡喜,怒濤般地席捲而來。這話聽起來或許十分誇張,但我真的只能這麼 形容。或許只是我個人妄想,但我真的覺得有一舉獲得原諒的感覺。

我最放不下的,就是電影《做夢機械》。電影本身固然如此,所有參與的工作人員也讓我非常的掛心。因為搞不好,一路上含辛茹苦畫出來的畫面,是非常可能再也無法被任何人看到的。因為原作、腳本、角色與世界觀的設定、分鏡、印象音樂等,所有的想法都在今敏一個人的心中。

當然了,有很多部分也是作畫監督、美術監督等等許多工作人員所共有的,但基本上這部作品只有今敏知道是在搞什麼,也只有今敏做的出來。如果說會變成這樣全都 是今敏的責任,那我也無話可說;但是我自認我也是付出了不少的努力,希望能跟大家一起分享這個世界觀的。事到如今,我的不對實在令我椎心刺骨地痛。

我真的覺得很對不起各位工作人員。但我希望你們稍微理解。因為今敏就是「這樣的人」,也才有辦法作出濃縮了許多與其他人不一樣成分的動畫。這說法或許十分傲慢,但請各位看在癌症的面子上就原諒我吧。

我並不是茫然地等死,我也在拼命地絞盡腦汁,好讓今敏亡後作品也能繼續存續。但這想法也太單純了。我跟丸山先生提到我對《做夢機械》的掛念,他只說了:「放心,我會替你想辦法的,不用擔心。」

我哭了。

我真的痛哭了。

過去在製作電影時、在編列預算時,都欠了他不少人情,最後總是丸山先生在替我收拾善後。這次也一樣,我一點進步都沒有。我跟丸山先生有很多時間長談。也因 此,我才稍微實際體會到,今敏的才能與技術在現在的動畫業界當中是十分珍貴的。我好惋惜這些才能。我說什麼都想要留下來。不過既然MAD HOUSE丸山先生都這麼說了,我總算能帶點自信,安心地走了。

確實,不用別人說我也單純地覺得,這怪點子以及細部描寫的技術就這麼消失了真的很可惜,但也沒辦法了。我衷心地感謝給了我站在世人面前機會的丸山先生。我真的很感謝你。以動畫導演身分而言,今敏也夠幸福的了。

告訴雙親時真的非常的痛苦。其實我也想趁著還能自由行動時,自己前往札幌,跟雙親報告我得了癌症這件事,但病情惡化的速度實在快得可惡,最後我只能在最接近 死亡的病房內,打了通唐突至極的電話告訴他們。「我得了胰臟癌,末期了,馬上就會死。能當爸爸媽媽的孩子我真的很幸福。謝謝你們。」突然說出口的話,並沒 有醞釀很久,畢竟當時我已經被將死的預感給包圍了。

直到我回到家,好不容易度過肺炎難關時。我下了很大的決心,才決定與雙親見面。雙親也很想見我。見面反倒痛苦,我也沒有氣力見面……但我說什麼都想看看他們 的臉。我想當面跟他們說,我很感謝他們生下我。我真的很幸福。雖然說我的生命走的比別人快了一點……這點讓我對妻子、對雙親、對我喜歡的人們都很不好意思。

他們很快地就回應了我的任性。第二天,我的雙親就從札幌趕到我家。剛看到我躺在床上,我媽脫口而出的那句話我畢生難忘。「對不起!我沒有把你生成一個健康的孩子!」我說不出第二句話。

跟雙親度過的時間並不算長,但已經夠了。我覺得他們看到我的臉,就能明白一切,事實上也是如此。

謝謝你們,爸爸,媽媽。能夠以你們兩人的孩子的身分誕生在這個世界上,是無比的幸福。數不盡的回憶以及感謝,充滿了我的胸膛。幸福本身也很可貴,但我更感激不盡的是,他們讓我培養出能感受到幸福的能力。真的很謝謝你們。

早父母一步先走非常不孝,不過這十幾年當中,我以動畫導演的身分充分施展自己的本領,達成了我的目標,也得到了相當的評價。唯一遺憾的是不算很賣座,但我覺得已經足以報答他們。特別是這十幾年來,我的生命密度是別人的好幾倍。這一點我相信雙親跟我一定都知道。

能夠跟雙親與丸山先生直接對話,讓我卸下了肩頭上的重擔。

最後,是比誰都讓我掛念,卻又直到最後都極力支撐我的妻子。接受醫生的宣告後,我們兩個人對泣數次。這段日子,每天對我們的身心都是煎熬。甚至無法用言詞形 容。可是,我之所以能夠熬過這些痛苦又無奈的日子,全都是因為醫生的宣告後,妳說的那番強而有力的話:「我會陪你走到最後。」

妳 這話一點都沒有錯。彷彿是要擺脫我的擔心似的,面對那些怒濤般從各處湧來的要求、請求,妳整理得井然有序,同時妳一下子就學會了如何照顧自己的丈夫。妳精 明幹練的模樣,讓我非常感動。「我的妻子好厲害啊!」都到這個地步就別說這些了?不不,是因為我深切體會到,妳比我一直以來所認為的都還要厲害。我相信在 我死了以後,妳一定也能很順利地將今敏送走。

回想起來,結婚後我每天都忙著工作工作,現在想想唯一悠閒地待在家裡的日子,就是罹癌之後,也真是太過分了。可是,我身旁的妳非常明白,忙於工作的人就是有所才能的人。我真的很幸福,真的。無論是活著的日子,還是迎接死亡的日子,我對妳的感謝都無法訴盡。謝謝妳。

還有很多事情讓我掛心的,但是一一細數就沒完沒了了。萬事都需要一個結束。最後,是我想現在應該很難接受的……答應讓我在家裡接受癌末照護的主治醫師H醫師,以及他的太太護理師K女士,我要對你們致上深深的謝意。雖然在家裡進行醫療是非常不方便的,但你們仍頑強地替我想出各種方法緩解癌症帶來的疼痛,在死亡逼近時你們也極力設法讓我過的更舒服一點,這真的幫了我很多。

不光是如此,面對這個不光是麻煩,態度也異常高傲的病患,你們跨越了工作的框框,用更人性化的方式幫助我們。真不知道該說是你們支撐著我們夫妻,還是拯救了我們。同時醫師賢伉儷的人品也不時地給了我們鼓勵。真的非常非常感謝你們。

這篇文章也到了最後了。在5月半知道我壽命所剩無幾時起,不分公私給了我們異乎尋常的盡力協助以及精神支援的兩位朋友,株式會社KON’STONE的成員、同時也是我高中時起的好朋友T先生,以及製作人H,我要衷心感謝你們。

真的很感謝你們。從我貧乏的語彙庫當中,很難找出適當的感謝詞,但我們夫妻都深受你們的照顧。如果沒有你們倆,我的死恐怕會更加痛苦,同時在一旁照顧我的妻 子也恐怕會我吞噬吧。真的一切都受你們的照顧了。儘管一直承蒙照顧,但不好意思,能夠請你們協助我的妻子,一直到我死後出殯嗎?這樣一來,我也能安心地啟 航了。我衷心地拜託你們。


最後,感謝一路閱讀這篇落落長文章的讀者,謝謝你們。我要懷著對世上所有美好事物的謝意,放下我的筆了。

先走一步了。

今敏

【譯/kinnsan】

Pre-Install

sudo apt-get install software-properties-common

https://packages.ubuntu.com/zh-tw/trusty/software-properties-common




Install Oracle Java8

sudo add-apt-repository "deb http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main"
sudo apt-get update
sudo apt-get install oracle-java8-installer
javac -version


Install Oracle Java9

sudo apt-get install oracle-java9-installer


Java version manager

sudo update-alternatives --config java


SET JAVA_HOME

Modified ~/.profile

vim ~/.profile

Append JAVA_HOME to environment

# JAVA_HOME
JAVA_HOME="/usr/lib/jvm/java-8-oracle"

# JAVA version manange controller aliases
alias jvc='sudo update-alternatives --config java'

Verify

source ~/.profile
echo $JAVA_HOME
jvc

jerry@local:~$ jvc
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                     Priority   Status
------------------------------------------------------------
  0            /usr/lib/jvm/java-9-oracle/bin/java       1091      auto mode
* 1            /usr/lib/jvm/java-8-oracle/jre/bin/java   1081      manual mode
  2            /usr/lib/jvm/java-9-oracle/bin/java       1091      manual mode

Press enter to keep the current choice[*], or type selection number: 1

Reference

https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-get-on-debian-8

thymeleaf 解析 html5 出錯

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>SB Admin - Start Bootstrap Template</title>
  <!-- Bootstrap core CSS-->
  <link href="../static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
  <!-- Custom fonts for this template-->
  <link href="../static/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
  <!-- Page level plugin CSS-->
  <link href="../static/vendor/datatables/dataTables.bootstrap4.css" rel="stylesheet">
  <!-- Custom styles for this template-->
  <link href="../static/css/sb-admin.css" rel="stylesheet">
</head>

SAXParseException

org.xml.sax.SAXParseException: 元素類型 "link" 必須由配對的結束標記 "</link>" 終止。
org.xml.sax.SAXParseException: 元素類型 "meta" 必須由配對的結束標記 "</meta>" 終止。

Solution

application.yml 設定

spring:
  thymeleaf:
    mode: LEGACYHTML5

看了一下 spring-boot-starter-thymeleaf 裡面用的 thymeleaf 的版本是 2.1.6

官網文件 - http://www.thymeleaf.org/doc/tutorials/2.1/extendingthymeleaf.html

XML: for XML that does not require validation during parsing.
VALIDXML: for XML that should be validated during parsing.
XHTML: for XHTML 1.0 or 1.1 templates that do not need validation.
VALIDXHTML: for XHTML 1.0 or 1.1 templates that should be validated during parsing.
HTML5: for HTML5 templates that are well-formed XML documents.
LEGACYHTML5: for HTML5 templates that are not well-formed XML documents, and therefore need a previous preprocessing step for tag balancing, syntax correction, etc.

所以改為 LEGACYHTML5, 但還需要搭配 nekohtml 套件。

pom.xml

<!-- https://mvnrepository.com/artifact/net.sourceforge.nekohtml/nekohtml -->
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>

Result

參考

Spring boot with docker

Spring-boot 官方教學:https://spring.io/guides/gs/spring-boot-docker/,
做法跟設定跟官方教學有些不太一樣, 不考慮用 maven plugin 維護 docker images, 方便 devOps 統一管理。

Set up Spring-boot

  1. 先建立 spring-boot 專案, 專案命名為 demo-docker-spring
  2. 建立 controller, 簡單 echo Hello Docker World


package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by jerry on 2018/1/15.
 *
 * @author jerry
 */
@Slf4j
@RestController
public class Controller {

    @RequestMapping("/")
    public String home() {
        return "Hello Docker World";
    }
}


Dockerfile

docker 官方不支援 oracle-java, 所以找了一個 star 較高的 image 來用。 Dockerfile 就直接放在專案底下。


# https://hub.docker.com/_/java/ 不支援 oracle-java
# FROM: 指定 base image (https://hub.docker.com/r/cogniteev/oracle-java/)
FROM cogniteev/oracle-java

# expose 8080 port
EXPOSE 8080

# 複製當前路徑所有資料, 到容器裡面
COPY . /usr/app/demo-docker-spring

# WORKDIR: 指定 docker 執行起來時候的預設目錄位置
WORKDIR /usr/app/demo-docker-spring

# 在容器裡面編譯
RUN ./mvnw clean package

# 指定容器啟動後執行的命令,並且不會被 docker run 提供的參數覆蓋。
ENTRYPOINT ["java","-jar","target/demo-docker-spring-0.0.1.jar"]

Docker build

# 以本地的 Dockerfile 為基礎, 建立 image
docker build -t jerry80409/demo-docker-spring:0.0.1 .

Docker run

docker run -d \
    --name demo-docker-spring \
    -p 8080:8080 \
    jerry80409/demo-docker-spring:0.0.1

心得

在容器裡面執行 ./mvnw clean package 會延長 container 啟動的時間, 萬一編譯失敗還要去看 docker logs 過濾, 比較好的做法還是在 host 處理相關的編譯與測試, 修改為

Dockerfile

# https://hub.docker.com/_/java/ 不支援 oracle-java
# FROM: 指定 base image (https://hub.docker.com/r/cogniteev/oracle-java/)
FROM cogniteev/oracle-java

# expose 8080 port
EXPOSE 8080

# 版本管理, 接收 docker build --build-arg 指定的參數
ARG APP_VERSION

# 複製編譯後的 jar , 到容器裡面
COPY target/${APP_VERSION} /usr/app/demo-docker-spring/app.jar

# WORKDIR: 指定 docker 執行起來時候的預設目錄位置
WORKDIR /usr/app/demo-docker-spring

# 在容器裡面編譯
# RUN ./mvnw clean package

# 指定容器啟動後執行的命令,並且不會被 docker run 提供的參數覆蓋。
ENTRYPOINT ["java","-jar","app.jar"]

Docker build

透過 --build-arg 動態指定編譯好的 jar 檔, 方便在不同版本號使用。

# 以本地的 Dockerfile 為基礎, 建立 image
docker build \
  --build-arg APP_VERSION=demo-docker-spring-0.0.1.jar \
  -t jerry80409/demo-docker-spring:0.0.1 .

Docker run

docker run -d \
    --name demo-docker-spring \
    -p 8080:8080 \
    jerry80409/demo-docker-spring:0.0.1

Docker


參考 GitBook - Docker Practice

鏡像(Image)和容器(Container)的關係,就像是 OOP (Object-oriented Programming) 中的 class 和 instance 一樣, Image 是靜態的定義, Container 是 Image 運行時的實體。容器可以被創建、啟動、停止、刪除、暫停等。

基本的運作方式像這樣, 參考 這份PPT


Developer Best Practice

Keep Images Small

確保 Images 越小越好, 跟 layers 越少越好, 這樣啟動的速度就越快.
Layers 指的是在 Dockerfile 的每一行指令, 代表一個 layer, 例如下面這份 Dockfile 就代表 6 Layers.

FROM ubuntu:15.04
COPY . /app
RUN apt-get -y update
RUN apt-get install -y python
RUN make /app
CMD python /app/app.py

可以修改為 4 layers.

FROM ubuntu:15.04
COPY . /app
RUN apt-get -y update && apt-get install -y python && make /app
CMD python /app/app.py

Layers 的說明: 參考



  • JDK 的開發者, 可以考慮 Docker 官方建議的 OpenJDK Image, 官方目前不支援 oracle-jdk 呦。
  • Multiple Build, 在一份 Dockerfile 同時使用不同來源的 image, 可以避免同時維護多個 dockerfiles, 參考:用 Docker Multi-Stage 編譯出 Go 語言最小 Image
  • 如果有多個 images 擁有相同的 common, 可以考慮建立一個 base image, 類似父層級的 image, docker 會協助做快取來加速其他 image 的啟動.
  • 在建立多個 images 時, 可以透過 tags 的指令做管理, 不要依賴 latest 的 tag.


Where and how to persist application data

  • 避免將資料存到 container 裡面, 這會導致 I/O 效率降低.
  • 建議使用 volumes, 將 host 的目錄或檔案掛載(mounted)到 docker 容器裡。
  • 敏感的資訊, 或 config 相關設定, 請考慮 docker secret

Differences between -v and --mount behavior

在閱讀文件的時候, 剛好看到自己的疑問, -v--mount 的差異,

  • -v - 永遠都會做 created direcotry 這個動作。
  • --mount - 如果檔案或目錄不存在, docker 會拋出 error。
透過 docker inspect devtest 可以看到詳細的差異。

volume 用法


docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/src:/app \ 
  nginx:latest

mount 用法


docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/src,target=/app \ 
  nginx:latest

Use swarm services when possible

這部分主要是建議以 群集(SWARM) 的架構使用 container, 目前還沒有深入的需求, 沒有深入研究



原因

打算從 instance 打包 logs 到 google cloud storage 發生了 AccessDeniedException: 403 Insufficient OAuth2 scope to perform this operation., 看起來是 instance 沒有 storage 權限

解決

Reference:

https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes

重新設定 service account 權限

instance 上內建有 gcloud, 就直接用現有的工具查詢一下 instance 的 account.

$ gsutil info 

或者在本機直接

gcloud compute instances describe INSTANCE_NAMES
Account: [alpha-number-compute@developer.gserviceaccount.com]
Project: [our-project-name]

會看到 instance 的一些狀態, 接下來就簡單多了, 按照下列的說明, 要先 stop instance, 更改 storage scope 再重新 start

To change an instance's service account and access scopes, the instance must be temporarily stopped. To stop your instance, read the documentation for Stopping an instance. After changing the service account or access scopes, remember to restart the instance.
# Stop Instance
gcloud compute instances stop INSTANCE_NAMES
# 設定 storage scope 為 full (Read, Write)
gcloud compute instances set-service-account INSTANCE_NAMES \
     --service-account alpha-number-compute@developer.gserviceaccount.com \
     --scopes storage-rw
# Start Instance
gcloud compute instances start INSTANCE_NAMES
# 再看一下有沒有設定成功
gcloud compute instances describe INSTANCE_NAMES

如果有多個 scope 要設定, 用 "," 分隔

gcloud compute instances set-service-account INSTANCE_NAMES \
     --service-account alpha-number-compute@developer.gserviceaccount.com \
     --scopes compute-rw,storage-rw

Scopes 參照表

default
    Scopes assigned to instances by default:
    - https://www.googleapis.com/auth/cloud.useraccounts.readonly
    - https://www.googleapis.com/auth/devstorage.read_only
    - https://www.googleapis.com/auth/logging.write
    - https://www.googleapis.com/auth/monitoring.write
    - https://www.googleapis.com/auth/pubsub
    - https://www.googleapis.com/auth/service.management.readonly
    - https://www.googleapis.com/auth/servicecontrol
    - https://www.googleapis.com/auth/trace.append
bigquery
    - https://www.googleapis.com/auth/bigquery
cloud-platform
    - https://www.googleapis.com/auth/cloud-platform
compute-ro
    - https://www.googleapis.com/auth/compute.readonly
compute-rw
    - https://www.googleapis.com/auth/compute
datastore
    - https://www.googleapis.com/auth/datastore
logging-write
    - https://www.googleapis.com/auth/logging.write
monitoring
    - https://www.googleapis.com/auth/monitoring
monitoring-write
    - https://www.googleapis.com/auth/monitoring.write
service-control
    - https://www.googleapis.com/auth/servicecontrol
service-management
    - https://www.googleapis.com/auth/service.management.readonly
sql-admin
    - https://www.googleapis.com/auth/sqlservice.admin
storage-full
    - https://www.googleapis.com/auth/devstorage.full_control
storage-ro
    - https://www.googleapis.com/auth/devstorage.read_only
storage-rw
    - https://www.googleapis.com/auth/devstorage.read_write
taskqueue
    - https://www.googleapis.com/auth/taskqueue
useraccounts-ro
    - https://www.googleapis.com/auth/cloud.useraccounts.readonly
useraccounts-rw
    - https://www.googleapis.com/auth/cloud.useraccounts
userinfo-email
    - https://www.googleapis.com/auth/userinfo.email