Memahami instruksi "VOLUME" di DockerFile

136

Di bawah ini adalah konten "Dockerfile" saya

FROM node:boron

# Create app directory
RUN mkdir -p /usr/src/app

# change working dir to /usr/src/app
WORKDIR /usr/src/app

VOLUME . /usr/src/app

RUN npm install

EXPOSE 8080

CMD ["node" , "server" ]

Dalam file ini saya mengharapkan instruksi "VOLUME. / Usr / src / app" untuk me-mount konten direktori kerja saat ini di host yang akan di-mount pada folder / usr / src / app wadah.

Tolong beri tahu saya jika ini cara yang benar?

refactor
sumber

Jawaban:

88

Tutorial buruh pelabuhan resmi mengatakan:

Volume data adalah direktori yang dirancang khusus dalam satu atau lebih kontainer yang melewati Sistem File Union. Volume data menyediakan beberapa fitur yang berguna untuk data persisten atau bersama:

  • Volume diinisialisasi ketika wadah dibuat. Jika gambar dasar wadah berisi data pada titik pemasangan yang ditentukan,
    data yang ada disalin ke volume baru setelah
    inisialisasi volume . (Perhatikan bahwa ini tidak berlaku saat memasang
    direktori host .)
  • Volume data dapat dibagikan dan digunakan kembali di antara kontainer.

  • Perubahan volume data dilakukan secara langsung.

  • Perubahan volume data tidak akan disertakan saat Anda memperbarui gambar.

  • Volume data tetap ada bahkan jika wadah itu sendiri dihapus.

Di DockerfileAnda hanya dapat menentukan tujuan volume di dalam wadah. mis /usr/src/app.

Ketika Anda menjalankan sebuah wadah, misalnya docker run --volume=/opt:/usr/src/app my_image, Anda mungkin tetapi tidak harus menentukan titik pemasangannya ( / opt ) pada mesin host. Jika Anda tidak menentukan --volumeargumen maka titik pemasangan akan dipilih secara otomatis, biasanya di bawah /var/lib/docker/volumes/.

Bukharov Sergey
sumber
277

Singkatnya: Tidak, VOLUMEinstruksi Anda tidak benar.

Dockerfile's VOLUMEmenentukan satu atau lebih volume yang diberikan jalur sisi wadah. Tapi itu tidak memungkinkan penulis gambar untuk menentukan jalur host. Di sisi host, volume dibuat dengan nama seperti ID yang sangat panjang di dalam akar Docker. Di mesin saya ini /var/lib/docker/volumes.

Catatan: Karena nama yang di-autogenerasi sangat panjang dan tidak masuk akal dari sudut pandang manusia, volume ini sering disebut sebagai "tanpa nama" atau "anonim".

Contoh Anda yang menggunakan '.' karakter bahkan tidak akan berjalan di mesin saya, tidak masalah jika saya membuat titik argumen pertama atau kedua. Saya mendapatkan pesan kesalahan ini:

buruh pelabuhan: Respons kesalahan dari daemon: kesalahan runtime oci: container_linux.go: 265: memulai proses wadah disebabkan "process_linux.go: 368: init kontainer disebabkan \" open / dev / ptmx: tidak ada file atau direktori seperti itu \ "".

Saya tahu bahwa apa yang telah dikatakan sampai saat ini mungkin tidak terlalu berharga bagi seseorang yang mencoba memahami VOLUME dan -vdan itu tentu saja tidak memberikan solusi untuk apa yang Anda coba capai. Jadi, mudah-mudahan, contoh-contoh berikut akan menjelaskan lebih lanjut tentang masalah ini.

Minitutorial: Menentukan volume

Diberikan Dockerfile ini:

FROM openjdk:8u131-jdk-alpine
VOLUME vol1 vol2

(Untuk hasil minitutorial ini, tidak ada bedanya jika kita menentukan vol1 vol2atau/vol1 /vol2 - jangan tanya kenapa)

Bangun itu:

docker build -t my-openjdk

Lari:

docker run --rm -it my-openjdk

Di dalam wadah, jalankan lsdi baris perintah dan Anda akan melihat dua direktori ada; /vol1dan/vol2 .

Menjalankan wadah juga menciptakan dua direktori, atau "volume", di sisi host.

Sambil menjalankan wadah, jalankan docker volume lsdi mesin host dan Anda akan melihat sesuatu seperti ini (saya telah mengganti bagian tengah nama dengan tiga titik untuk singkatnya):

DRIVER    VOLUME NAME
local     c984...e4fc
local     f670...49f0

Kembali ke dalam wadah , jalankan touch /vol1/weird-ass-file(buat file kosong di lokasi tersebut).

File ini sekarang tersedia di mesin host, di salah satu volume lol yang tidak disebutkan namanya. Butuh saya dua kali mencoba karena saya pertama kali mencoba volume yang tercantum pertama, tetapi akhirnya saya menemukan file saya di volume yang tercantum kedua, menggunakan perintah ini pada mesin host:

sudo ls /var/lib/docker/volumes/f670...49f0/_data

Demikian pula, Anda dapat mencoba menghapus file ini pada host dan itu akan dihapus dalam wadah juga.

Catatan: _dataFolder ini juga disebut sebagai "titik mount".

Keluar dari wadah dan daftarkan volume pada host. Mereka sudah pergi. Kami menggunakan --rmbendera saat menjalankan wadah dan opsi ini secara efektif menghapus tidak hanya wadah saat keluar, tetapi juga volume.

Jalankan wadah baru, tetapi tentukan volume menggunakan -v:

docker run --rm -it -v /vol3 my-openjdk

Ini menambahkan volume ketiga dan seluruh sistem akhirnya memiliki tiga volume yang tidak disebutkan namanya. Perintahnya akan crash jika kita tentukan saja -v vol3. Argumen harus menjadi jalur absolut di dalam wadah. Di sisi host, volume ketiga yang baru bersifat anonim dan berada bersama dengan dua volume lainnya di /var/lib/docker/volumes/.

Telah dinyatakan sebelumnya bahwa Dockerfiletidak dapat memetakan ke jalur host yang semacam menimbulkan masalah bagi kami ketika mencoba untuk membawa file dari host ke wadah selama runtime. -vSintaks yang berbeda memecahkan masalah ini.

Bayangkan saya memiliki subfolder di direktori proyek ./srcsaya yang ingin saya sinkronkan ke /srcdalam wadah. Perintah ini melakukan trik:

docker run -it -v $(pwd)/src:/src my-openjdk

Kedua sisi :karakter mengharapkan jalur absolut. Sisi kiri menjadi jalur absolut pada mesin host, sisi kanan menjadi jalur absolut di dalam wadah. pwdadalah perintah yang "mencetak direktori saat ini / bekerja". Memasukkan perintah $()mengambil perintah dalam tanda kurung, menjalankannya dalam subkulit dan mengembalikan jalur absolut ke direktori proyek kami.

Menyatukan semuanya, asumsikan ada ./src/Hello.javadi folder proyek kami di mesin host dengan konten berikut:

public class Hello {
    public static void main(String... ignored) {
        System.out.println("Hello, World!");
    }
}

Kami membangun Dockerfile ini:

FROM openjdk:8u131-jdk-alpine
WORKDIR /src
ENTRYPOINT javac Hello.java && java Hello

Kami menjalankan perintah ini:

docker run -v $(pwd)/src:/src my-openjdk

Ini mencetak "Halo, Dunia!".

Bagian terbaiknya adalah kita benar-benar bebas untuk memodifikasi file .java dengan pesan baru untuk output lain pada proses kedua - tanpa harus membangun kembali gambar =)

Komentar akhir

Saya cukup baru untuk Docker, dan "tutorial" yang disebutkan di atas mencerminkan informasi yang saya kumpulkan dari hackathon baris perintah 3 hari. Saya hampir malu saya belum dapat memberikan tautan ke dokumentasi yang jelas seperti bahasa Inggris yang mendukung pernyataan saya, tetapi saya jujur ​​berpikir ini disebabkan oleh kurangnya dokumentasi dan bukan upaya pribadi. Saya tahu contoh berfungsi seperti yang diiklankan menggunakan pengaturan saya saat ini yaitu "Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce".

Tutorial tidak menyelesaikan masalah "bagaimana kita menentukan jalur wadah di Dockerfile dan biarkan perintah jalankan hanya menentukan jalur host". Mungkin ada cara, saya belum menemukannya.

Akhirnya, saya punya firasat bahwa menentukan VOLUMEdi Dockerfile bukan hanya tidak biasa, tetapi mungkin merupakan praktik terbaik untuk tidak pernah menggunakan VOLUME. Karena dua alasan. Alasan pertama yang telah kami identifikasi: Kami tidak dapat menentukan jalur host - yang merupakan hal yang baik karena Dockerfiles harus sangat agnostik dengan spesifikasi mesin host. Tapi alasan kedua adalah orang mungkin lupa menggunakan --rmopsi saat menjalankan wadah. Orang mungkin ingat untuk menghapus wadah tetapi lupa untuk menghapus volume. Plus, bahkan dengan ingatan manusia terbaik, mungkin merupakan tugas yang menakutkan untuk mencari tahu mana dari semua volume anonim yang aman untuk dihapus.

Martin Andersson
sumber
2
Kapan kita harus menggunakan volume yang tidak disebutkan namanya / anonim?
Searene
10
@ Martin, terima kasih banyak. Hackathon Anda dan tutorial yang dihasilkannya di sini sangat dihargai.
Beezer
6
"Saya belum dapat memberikan tautan ke dokumentasi yang mirip dengan Bahasa Inggris ... Saya benar-benar berpikir ini disebabkan oleh kurangnya dokumentasi". Saya bisa konfirmasi. Ini adalah dokumentasi yang paling menyeluruh dan terkini yang saya temukan dan saya telah mencari berjam-jam.
user697576
4
docker volume prunedapat digunakan untuk membersihkan volume sisa yang tidak terpasang ke wadah yang sedang berjalan. Bukan untuk mengatakan bahwa itu akan mudah untuk distingiush yang berpotensi penting oleh id saja ...
Jeremy
4
"Untuk hasil dari minitutorial ini, tidak ada bedanya jika kita menentukan vol1 vol2 atau / vol1 / vol2 - jangan tanya kenapa". @ MartinAndersson itu karena direktori kerja saat ini /, vol1relatif terhadap /, yang memutuskan untuk /vol1. Jika Anda menggunakan WORKDIRuntuk menentukan direktori yang berfungsi selain /, vol1dan /vol1tidak lagi akan menunjuk ke direktori yang sama.
sebastian
41

Menentukan VOLUMEbaris dalam Dockerfile mengkonfigurasi sedikit metadata pada gambar Anda, tetapi bagaimana metadata itu digunakan adalah penting.

Pertama, apa yang dilakukan dua baris ini:

WORKDIR /usr/src/app
VOLUME . /usr/src/app

The WORKDIRgaris ada menciptakan direktori jika tidak ada, dan update beberapa metadata gambar untuk menentukan semua path relatif, bersama dengan direktori saat ini untuk perintah seperti RUNakan berada di lokasi tersebut. The VOLUMEgaris ada menetapkan dua volume , satu adalah path relatif ., dan yang lainnya /usr/src/app, baik hanya kebetulan berada direktori yang sama. Paling sering VOLUMEbaris hanya berisi direktori tunggal, tetapi bisa berisi banyak seperti yang Anda lakukan, atau bisa berupa array yang diformat json.

Anda tidak dapat menentukan sumber volume di Dockerfile : Sumber kebingungan yang umum ketika menentukan volume di Dockerfile mencoba mencocokkan sintaks runtime sumber dan tujuan pada waktu pembuatan gambar, ini tidak akan berfungsi . Dockerfile hanya dapat menentukan tujuan volume. Ini akan menjadi eksploitasi keamanan sepele jika seseorang dapat menentukan sumber volume karena mereka dapat memperbarui gambar umum pada hub buruh pelabuhan untuk memasang direktori root ke dalam wadah dan kemudian meluncurkan proses latar belakang di dalam wadah sebagai bagian dari titik masuk yang menambahkan login ke / etc / passwd, mengkonfigurasi systemd untuk meluncurkan penambang bitcoin pada reboot berikutnya, atau mencari filesystem untuk kartu kredit, SSN, dan kunci pribadi untuk dikirim ke situs remote.

Apa yang dilakukan garis VOLUME? Seperti disebutkan, ini menetapkan beberapa metadata gambar untuk mengatakan direktori di dalam gambar adalah volume. Bagaimana metadata ini digunakan? Setiap kali Anda membuat wadah dari gambar ini, buruh pelabuhan akan memaksa direktori itu menjadi volume. Jika Anda tidak memberikan volume dalam perintah jalankan Anda, atau menulis file, satu-satunya pilihan untuk buruh pelabuhan adalah membuat volume anonim. Ini adalah volume bernama lokal dengan id unik panjang untuk nama dan tidak ada indikasi lain mengapa itu dibuat atau data apa yang dikandungnya (volume anonim adalah data yang hilang). Jika Anda mengganti volume, menunjuk ke volume bernama atau host, data Anda akan pergi ke sana.

VOLUME memecahkan beberapa hal: Anda tidak dapat menonaktifkan volume yang pernah didefinisikan dalam Dockerfile. Dan yang lebih penting, RUNperintah di buruh pelabuhan diimplementasikan dengan wadah sementara. Kontainer sementara itu akan mendapatkan volume anonim sementara. Volume anonim itu akan diinisialisasi dengan konten gambar Anda. Setiap tulisan di dalam wadah dari RUNperintah Anda akan dibuat ke volume itu. Ketika RUNperintah selesai, perubahan pada gambar disimpan, dan perubahan volume anonim dibuang. Karena itu, saya sangat menyarankan untuk tidak mendefinisikan VOLUMEdi dalam Dockerfile. Ini menghasilkan perilaku tak terduga untuk pengguna hilir gambar Anda yang ingin memperluas gambar dengan data awal di lokasi volume.

Bagaimana Anda menentukan volume? Untuk menentukan di mana Anda ingin memasukkan volume dengan gambar Anda, berikan a docker-compose.yml. Pengguna dapat memodifikasi itu untuk menyesuaikan lokasi volume dengan lingkungan lokal mereka, dan itu menangkap pengaturan runtime lain seperti port penerbitan dan jaringan.

Seseorang harus mendokumentasikan ini! Mereka punya. Docker menyertakan peringatan tentang penggunaan VOLUME dalam dokumentasi mereka di Dockerfile bersama dengan saran untuk menentukan sumber saat runtime:

  • Mengubah volume dari dalam Dockerfile: Jika langkah membangun apa pun mengubah data dalam volume setelah dideklarasikan, perubahan tersebut akan dibuang.

...

  • Direktori host dideklarasikan pada run-time kontainer: Direktori host (mountpoint), pada dasarnya, bergantung pada host. Ini untuk menjaga portabilitas gambar, karena direktori host yang diberikan tidak dapat dijamin tersedia di semua host. Karena alasan ini, Anda tidak dapat memasang direktori host dari dalam Dockerfile. The VOLUME instruksi tidak mendukung menentukan host-dirparameter. Anda harus menentukan titik mount ketika Anda membuat atau menjalankan wadah.
BMitch
sumber
36

The VOLUMEperintah dalam Dockerfilecukup legit, benar-benar konvensional, baik-baik saja untuk digunakan dan tidak usang dalam pula. Hanya perlu memahaminya.

Kami menggunakannya untuk menunjuk ke direktori mana pun yang akan ditulis oleh aplikasi dalam wadah. Kami tidak menggunakan VOLUMEhanya karena kami ingin berbagi antara host dan wadah seperti file konfigurasi.

Perintah hanya membutuhkan satu param; jalur ke folder, relatif WORKDIRjika diatur, dari dalam wadah. Kemudian buruh pelabuhan akan membuat volume dalam grafiknya (/ var / lib / docker) dan memasangnya ke folder dalam wadah. Sekarang wadah akan memiliki tempat untuk menulis dengan kinerja tinggi. Tanpa VOLUMEperintah, kecepatan tulis ke folder yang ditentukan akan sangat lambat karena sekarang wadah menggunakan copy on writestrategi itu dalam wadah itu sendiri. The copy on writestrategi adalah alasan utama mengapa volume ada.

Jika Anda me-mount folder yang ditentukan oleh VOLUMEperintah, perintah tidak pernah dijalankan karena VOLUMEhanya dieksekusi ketika wadah mulai, jenis suka ENV.

Pada dasarnya dengan VOLUMEperintah Anda mendapatkan kinerja tanpa memasang volume apa pun secara eksternal. Data akan menghemat lintas wadah juga tanpa adanya pemasangan eksternal. Kemudian ketika siap, cukup pasang sesuatu di atasnya.

Beberapa contoh kasus penggunaan yang bagus:
- log
- temp folder

Beberapa kasus penggunaan buruk:
- file statis
- konfigurasi
- kode

surga
sumber
2
Mengenai contoh kasus penggunaan yang baik dan buruk, halaman "dockerfile best-practices" Docker mengatakan: "Anda sangat dianjurkan untuk menggunakan VOLUME untuk bagian gambar Anda yang dapat diubah dan / atau dapat diperbaiki pengguna.". Saya pikir konfigurasi ada di sana.
OmerSch
2
Tidak apa-apa untuk menjadi eksplisit tentang VOLUMEdirektori untuk konfigurasi. Namun begitu Anda benar-benar me-mount konfigurasi Anda harus me-mount direktori itu dan oleh karena itu VOLUMEperintah tidak berjalan. Oleh karena itu tidak ada gunanya menggunakan VOLUMEperintah pada direktori yang ditentukan untuk konfigurasi. Juga menginisialisasi grafik volume dengan file hanya baca statis statis adalah pembunuhan berat. Jadi saya mendukung apa yang saya katakan, tidak perlu VOLUMEperintah pada konfigurasi.
Tuan surga
Volume dapat membawa karakteristik kinerja yang berbeda karena detail implementasi. File data basis data sesuai dengan kasus penggunaan ini, tetapi apa gunanya menyimpan data di samping penyimpanan kontainer (sesaat)? Yaitu mengaitkan keberadaan volume dengan kinerja tidak benar.
André Werlang
33

Untuk lebih memahami volumeinstruksi di dockerfile, mari kita pelajari penggunaan volume tipikal dalam implementasi file docker resmi mysql.

VOLUME /var/lib/mysql

Referensi: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

Ini /var/lib/mysqladalah lokasi default MySQL yang menyimpan file data.

Ketika Anda menjalankan wadah uji hanya untuk tujuan pengujian, Anda mungkin tidak menentukan titik pemasangannya, mis

docker run mysql:8

maka instance container mysql akan menggunakan path mount default yang ditentukan oleh volumeinstruksi di dockerfile. volume dibuat dengan nama seperti ID yang sangat panjang di dalam akar Docker, ini disebut volume "tidak bernama" atau "anonim". Dalam folder sistem host / var / lib / docker / volume yang mendasarinya.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Ini sangat nyaman untuk keperluan pengujian cepat tanpa perlu menentukan titik pemasangan, tetapi masih bisa mendapatkan kinerja terbaik dengan menggunakan Volume untuk penyimpanan data, bukan lapisan kontainer.

Untuk penggunaan formal, Anda harus menentukan jalur pemasangan dengan menggunakan volume yang dinamai atau bind mount, mis

docker run  -v /my/own/datadir:/var/lib/mysql mysql:8

Perintah me-mount direktori / my / own / datadir dari sistem host yang mendasarinya sebagai / var / lib / mysql di dalam container. Direktori data / my / own / datadir tidak akan dihapus secara otomatis, walaupun container dihapus.

Penggunaan gambar resmi mysql (Silakan periksa bagian "Di mana Menyimpan Data"):

Referensi: https://hub.docker.com/_/mysql/

Li-Tian
sumber
2
Saya sangat suka penjelasan Anda.
LukaszTaraszka
Tapi buruh pelabuhan menyimpan perubahan. Anda juga dapat mengatur jalur mount -vmenggunakannya tanpa mengatur volume di Dockerfile
Alex78191
1

Saya tidak menganggap penggunaan VOLUME bagus dalam hal apa pun, kecuali jika Anda membuat gambar untuk diri sendiri dan tidak ada orang lain yang akan menggunakannya.

Saya terkena dampak negatif karena VOLUME terpapar dalam gambar dasar yang saya perpanjang dan hanya muncul untuk mengetahui tentang masalah setelah gambar sudah berjalan, seperti wordpress yang menyatakan /var/www/htmlfolder sebagai VOLUME , dan ini berarti bahwa ada file yang ditambahkan atau diubah selama tahap pembuatan tidak dipertimbangkan, dan perubahan langsung tetap ada, bahkan jika Anda tidak tahu. Ada solusi buruk untuk menentukan direktori web di tempat lain, tetapi ini hanyalah solusi buruk untuk yang harus lebih sederhana: cukup hapus arahan VOLUME.

Anda dapat mencapai maksud volume dengan mudah menggunakan -vopsi ini, ini tidak hanya memperjelas volume wadah (tanpa harus melihat Dockerfile dan induk Dockerfiles), tetapi ini juga memberikan konsumen pilihan untuk gunakan volume atau tidak.

Ini pada dasarnya buruk untuk menggunakan VOLUMES karena alasan berikut, seperti yang dikatakan oleh jawaban ini :

Namun, instruksi VOLUME memang harus dibayar mahal.

  • Pengguna mungkin tidak menyadari volume yang tidak disebutkan namanya sedang dibuat, dan terus mengambil ruang penyimpanan pada host Docker mereka setelah kontainer dilepas.
  • Tidak ada cara untuk menghapus volume yang dideklarasikan di Dockerfile. Gambar hilir tidak dapat menambahkan data ke jalur di mana volume ada.

Masalah yang terakhir menghasilkan masalah seperti ini.

Memiliki opsi untuk tidak mendeklarasikan volume akan membantu, tetapi hanya jika Anda tahu volume yang ditentukan dalam dockerfile yang menghasilkan gambar (dan parent dockerfiles!). Lebih jauh, VOLUME dapat ditambahkan dalam versi Dockerfile yang lebih baru dan memecahkan banyak hal secara tidak terduga untuk konsumen gambar.

Penjelasan bagus lainnya ( tentang gambar oracle yang memiliki VOLUME , yang telah dihapus ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Lebih banyak kasus di mana VOLUME memecahkan barang untuk orang:

Sebuah permintaan tarik untuk menambahkan opsi untuk me-reset sifat gambar orangtua (termasuk VOLUME), ditutup dan sedang dibahas di sini (dan Anda dapat melihat beberapa kasus dari orang terpengaruh karena volume didefinisikan dalam dockerfiles), yang memiliki komentar dengan baik a penjelasan terhadap VOLUME:

Menggunakan VOLUME di Dockerfile tidak ada gunanya. Jika pengguna membutuhkan kegigihan, mereka pasti akan memberikan pemetaan volume saat menjalankan wadah yang ditentukan. Sangat sulit untuk melacak bahwa masalah saya tidak dapat mengatur kepemilikan direktori (/ var / lib / influxdb) adalah karena deklarasi VOLUME di Dockerfile milik InfluxDB. Tanpa jenis opsi UNVOLUME, atau menyingkirkannya sama sekali, saya tidak dapat mengubah apa pun yang terkait dengan folder yang ditentukan. Ini kurang dari ideal, terutama ketika Anda sadar akan keamanan dan keinginan untuk menentukan UID tertentu gambar harus dijalankan sebagai, untuk menghindari pengguna acak, dengan lebih banyak izin dari yang diperlukan, menjalankan perangkat lunak pada host Anda.

Saya juga menganggap EXPOSE buruk, tetapi memiliki sedikit efek samping. Satu-satunya hal baik yang dapat saya lihat tentang VOLUME dan EXPOSE adalah tentang dokumentasi, dan saya akan menganggap mereka baik jika mereka hanya melayani untuk itu (tanpa efek samping).

TL; DR

Saya menganggap bahwa penggunaan VOLUME terbaik harus ditinggalkan.

Lucas Basquerotto
sumber