Zrozumienie instrukcji „VOLUME” w DockerFile


150

Poniżej znajduje się zawartość mojego „Dockerfile”

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" ]

W tym pliku oczekuję instrukcji "VOLUME. / Usr / src / app", aby zamontować zawartość obecnego katalogu roboczego na hoście do zamontowania w folderze / usr / src / app kontenera.

Daj mi znać, jeśli to właściwy sposób?

Odpowiedzi:


104

Oficjalny samouczek dotyczący platformy dokującej mówi:

Wolumen danych to specjalnie wyznaczony katalog w co najmniej jednym kontenerze, który omija Union File System. Woluminy danych zapewniają kilka przydatnych funkcji dla danych trwałych lub udostępnianych:

  • Woluminy są inicjowane podczas tworzenia kontenera. Jeśli obraz podstawowy kontenera zawiera dane w określonym punkcie instalacji,
    te istniejące dane są kopiowane do nowego woluminu po
    zainicjowaniu woluminu . (Należy zauważyć, że nie dotyczy to montowania
    katalogu hosta ).

  • Woluminy danych można udostępniać i ponownie wykorzystywać między kontenerami.

  • Zmiany objętości danych są dokonywane bezpośrednio.

  • Zmiany ilości danych nie zostaną uwzględnione podczas aktualizacji obrazu.

  • Woluminy danych są trwałe, nawet jeśli sam kontener zostanie usunięty.

W Dockerfilemożna określić tylko miejsce docelowe objętości wewnątrz kontenera. np /usr/src/app.

Po uruchomieniu pojemnik, np docker run --volume=/opt:/usr/src/app my_image, to mogą , ale nie muszą podać swój punkt montażowy ( /opt) na komputerze hosta. Jeśli nie podasz --volumeargumentu, punkt montowania zostanie wybrany automatycznie, zwykle pod /var/lib/docker/volumes/.


Niesamowite wyjaśnienie na wideo, jak działa pamięć masowa w Dockerze
Walter

In Dockerfile you can specify only the destination of a volume inside a container. e.g. /usr/src/app.to jest dokładnie ten wiersz, który musiałem przeczytać, aby przestać inwestować więcej czasu w to rozwiązanie
Dalmiro Granas

303

Krótko mówiąc: nie, twoja VOLUMEinstrukcja jest nieprawidłowa.

Pliki Dockerfile VOLUMEokreślają jeden lub więcej woluminów ze ścieżkami po stronie kontenera. Ale nie pozwala autorowi obrazu określić ścieżki hosta. Po stronie hosta woluminy są tworzone z bardzo długą nazwą podobną do identyfikatora w katalogu głównym platformy Docker. To jest na mojej maszynie /var/lib/docker/volumes.

Uwaga: ponieważ automatycznie generowana nazwa jest niezwykle długa i nie ma sensu z ludzkiego punktu widzenia, tomy te są często określane jako „bez nazwy” lub „anonimowe”.

Twój przykład, w którym zastosowano „.” znak nie będzie nawet działał na moim komputerze, bez względu na to, czy kropka będzie pierwszym, czy drugim argumentem. Otrzymuję ten komunikat o błędzie:

docker: Odpowiedź błędu z programu daemon: oci runtime error: container_linux.go: 265: uruchomienie procesu kontenera spowodowało "process_linux.go: 368: inicjalizacja kontenera spowodowała \" otwarcie / dev / ptmx: brak takiego pliku lub katalogu \ "".

Wiem, że to, co zostało do tej pory powiedziane, prawdopodobnie nie jest zbyt wartościowe dla kogoś, kto próbuje zrozumieć, VOLUMEiz -vpewnością nie dostarcza rozwiązania tego, co próbujesz osiągnąć. Miejmy nadzieję, że poniższe przykłady rzucą więcej światła na te kwestie.

Mały samouczek: określanie objętości

Biorąc pod uwagę ten plik Dockerfile:

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

(Jeśli chodzi o wynik tego mini samouczka, nie ma znaczenia, czy określimy, vol1 vol2czy /vol1 /vol2- nie pytaj mnie dlaczego)

Zbuduj to:

docker build -t my-openjdk

Biegać:

docker run --rm -it my-openjdk

Wewnątrz kontenera uruchom lsw wierszu poleceń, a zauważysz, że istnieją dwa katalogi; /vol1i /vol2.

Uruchomienie kontenera powoduje również utworzenie dwóch katalogów lub „woluminów” po stronie hosta.

Mając uruchomiony kontener, wykonaj docker volume lsna maszynie hosta, a zobaczysz coś takiego (zastąpiłem środkową część nazwy trzema kropkami dla zwięzłości):

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

Z powrotem w kontenerze wykonaj touch /vol1/weird-ass-file(tworzy pusty plik w tej lokalizacji).

Ten plik jest teraz dostępny na komputerze głównym, w jednym z nienazwanych woluminów lol. Zajęło mi to dwie próby, ponieważ najpierw wypróbowałem pierwszy wymieniony wolumin, ale ostatecznie znalazłem mój plik w drugim wymienionym woluminie, używając tego polecenia na komputerze hosta:

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

Podobnie możesz spróbować usunąć ten plik na hoście, a zostanie on również usunięty z kontenera.

Uwaga: _datafolder jest również nazywany „punktem montowania”.

Wyjdź z kontenera i wyświetl woluminy na hoście. Oni odeszli. Użyliśmy --rmflagi podczas uruchamiania kontenera i ta opcja skutecznie usuwa nie tylko kontener przy wyjściu, ale także woluminy.

Uruchom nowy kontener, ale określ wolumen za pomocą -v:

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

To dodaje trzeci wolumin, a cały system ma trzy nienazwane woluminy. Polecenie zawiesiłoby się, gdybyśmy tylko określili -v vol3. Argument musi być bezwzględną ścieżką wewnątrz kontenera. Po stronie hosta nowy trzeci tom jest anonimowy i znajduje się wraz z pozostałymi dwoma tomami w formacie /var/lib/docker/volumes/.

Wspomniano wcześniej, że Dockerfilenie można mapować na ścieżkę hosta, co w pewnym sensie stanowi dla nas problem podczas próby przeniesienia plików z hosta do kontenera w czasie wykonywania. Inna -vskładnia rozwiązuje ten problem.

Wyobraź sobie, że mam podfolder w katalogu projektu, ./srcktóry chcę zsynchronizować /srcwewnątrz kontenera. To polecenie załatwia sprawę:

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

Obie strony :postaci oczekują bezwzględnej ścieżki. Lewa strona to bezwzględna ścieżka na komputerze hosta, a prawa strona to bezwzględna ścieżka wewnątrz kontenera. pwdjest poleceniem, które "wypisuje bieżący / roboczy katalog". Umieszczenie polecenia $()powoduje umieszczenie polecenia w nawiasach, uruchomienie go w podpowłoce i zwrócenie bezwzględnej ścieżki do naszego katalogu projektu.

Podsumowując to wszystko, załóżmy, że mamy ./src/Hello.javaw naszym folderze projektu na komputerze głównym z następującą zawartością:

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

Budujemy ten plik Dockerfile:

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

Uruchamiamy to polecenie:

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

To wypisuje "Hello, World!".

Najlepsze jest to, że możemy całkowicie zmodyfikować plik .java za pomocą nowej wiadomości dla innego wyjścia przy drugim uruchomieniu - bez konieczności przebudowywania obrazu =)

Uwagi końcowe

Jestem całkiem nowy w Dockerze, a wspomniany wyżej „samouczek” odzwierciedla informacje zebrane podczas 3-dniowego hackathonu z użyciem wiersza poleceń. Jestem prawie zawstydzony, że nie udało mi się podać linków do przejrzystej dokumentacji w języku angielskim, która zawierałaby moje oświadczenia, ale szczerze myślę, że jest to spowodowane brakiem dokumentacji, a nie osobistym wysiłkiem. Wiem, że przykłady działają zgodnie z reklamą, używając mojej obecnej konfiguracji, czyli „Windows 10 -> Vagrant 2.0.0 -> Docker 17.09.0-ce”.

Samouczek nie rozwiązuje problemu „jak określić ścieżkę kontenera w pliku Dockerfile i pozwolić komendzie uruchamiania określić tylko ścieżkę hosta”. Może istnieć sposób, po prostu go nie znalazłem.

Wreszcie mam przeczucie, że określanie VOLUMEw pliku Dockerfile jest nie tylko rzadkie, ale prawdopodobnie najlepszym rozwiązaniem jest nigdy nie używać VOLUME. Z dwóch powodów. Pierwszy powód, który już zidentyfikowaliśmy: nie możemy określić ścieżki hosta - co jest dobrą rzeczą, ponieważ pliki Dockerfiles powinny być bardzo niezależne od specyfiki maszyny hosta. Ale drugim powodem jest to, że ludzie mogą zapomnieć o użyciu --rmopcji podczas uruchamiania kontenera. Można by pamiętać o wyjęciu pojemnika, ale zapomnieć o usunięciu objętości. Ponadto, nawet przy najlepszej pamięci ludzkiej, ustalenie, które ze wszystkich anonimowych woluminów można bezpiecznie usunąć, może być trudnym zadaniem.


2
Kiedy powinniśmy używać nienazwanych / anonimowych woluminów?
Searene

10
@ Martin bardzo dziękuję. Twój hackathon i wynikający z niego samouczek są bardzo doceniane.
Beezer

6
„Nie byłem w stanie podać linków do przejrzystej dokumentacji w stylu angielskim… Szczerze mówiąc uważam, że jest to spowodowane brakiem dokumentacji”. Mogę potwierdzić. To najbardziej dokładna i aktualna dokumentacja, jaką znalazłem i szukałem godzinami.
user697576

4
docker volume prunemoże służyć do czyszczenia pozostałych woluminów, które nie są dołączone do działających kontenerów. Nie mówiąc o tym, że łatwo będzie odróżnić potencjalnie ważne osoby za pomocą samego identyfikatora ...
Jeremy,

4
„Dla wyniku tego mini-samouczka nie ma znaczenia, czy podamy vol1 vol2 czy / vol1 / vol2 - nie pytaj mnie dlaczego”. @MartinAndersson to dlatego, że bieżący katalog roboczy jest /, więc vol1jest względny /, co jest rozpoznawane jako /vol1. Jeśli używasz WORKDIRokreślić katalog roboczy inny niż /, vol1i /vol1już nie będzie wskazywać na tym samym katalogu.
sebastian

55

Określenie VOLUMEwiersza w pliku Dockerfile konfiguruje trochę metadanych w obrazie, ale sposób wykorzystania tych metadanych jest ważny.

Po pierwsze, co zrobiły te dwie linie:

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

WORKDIRLinia nie tworzy katalog, jeśli nie istnieje, i aktualizuje niektóre metadane obrazu określić wszystkie ścieżki względne, wraz z bieżącym katalogu dla komend jak RUNbędzie w tym miejscu. Znajdujący się VOLUMEtam wiersz określa dwa woluminy , jeden jest ścieżką względną ., a drugi /usr/src/app, oba są po prostu tym samym katalogiem. Najczęściej VOLUMEwiersz zawiera tylko jeden katalog, ale może zawierać wiele, jak już zrobiłeś, lub może to być tablica w formacie JSON.

Nie można określić źródła woluminu w pliku Dockerfile : częstym źródłem nieporozumień podczas określania woluminów w pliku Dockerfile jest próba dopasowania składni środowiska wykonawczego źródła i miejsca docelowego w czasie kompilacji obrazu, to nie zadziała . Plik Dockerfile może określać tylko miejsce docelowe woluminu. Byłby to trywialny exploit bezpieczeństwa, gdyby ktoś mógł zdefiniować źródło woluminu, ponieważ mógłby zaktualizować wspólny obraz w centrum docker, aby zamontować katalog główny w kontenerze, a następnie uruchomić proces w tle wewnątrz kontenera jako część punktu wejścia, który dodaje loginy do / etc / passwd, konfiguruje systemd do uruchomienia koparki bitcoinów przy następnym restarcie lub przeszukuje system plików w poszukiwaniu kart kredytowych, SSN i ​​kluczy prywatnych w celu wysłania do zdalnej strony.

Co robi linia VOLUME? Jak wspomniano, niektóre metadane obrazu wskazują, że katalog wewnątrz obrazu jest woluminem. Jak są używane te metadane? Za każdym razem, gdy tworzysz kontener z tego obrazu, docker wymusi, że katalog będzie woluminem. Jeśli nie podasz woluminu w poleceniu uruchomienia ani nie utworzysz pliku, jedyną opcją dla dockera jest utworzenie anonimowego woluminu. Jest to lokalny nazwany wolumin z długim unikalnym identyfikatorem dla nazwy i bez innego wskazania, dlaczego został utworzony lub jakie dane zawiera (anonimowe woluminy są danymi, w których dane są tracone). Jeśli zastąpisz wolumin, wskazując na wolumin nazwany lub hosta, Twoje dane trafią tam.

VOLUME psuje rzeczy: nie można wyłączyć woluminu zdefiniowanego w pliku Dockerfile. Co ważniejsze, RUNpolecenie w dockerze jest realizowane z tymczasowymi kontenerami. Te tymczasowe pojemniki otrzymają tymczasowy anonimowy wolumin. Ten anonimowy wolumin zostanie zainicjowany zawartością Twojego obrazu. Wszelkie zapisy w kontenerze z twojego RUNpolecenia zostaną wykonane na tym woluminie. Po zakończeniu wykonywania RUNpolecenia zmiany w obrazie są zapisywane, a zmiany w woluminie anonimowym są odrzucane. Z tego powodu zdecydowanie odradzam definiowanie pliku VOLUMEwewnątrz Dockerfile. Powoduje to nieoczekiwane zachowanie dalszych użytkowników obrazu, którzy chcą rozszerzyć obraz o początkowe dane w lokalizacji woluminu.

Jak określić głośność? Aby określić, gdzie chcesz dołączyć woluminy do obrazu, podaj plik docker-compose.yml. Użytkownicy mogą to zmodyfikować, aby dostosować lokalizację woluminu do lokalnego środowiska, a także przechwytuje inne ustawienia środowiska wykonawczego, takie jak publikowanie portów i sieci.

Ktoś powinien to udokumentować! Oni mają. Docker zawiera ostrzeżenia dotyczące użycia VOLUME w dokumentacji w pliku Dockerfile wraz z poradą dotyczącą określenia źródła w czasie wykonywania:

  • Zmiana woluminu z poziomu pliku Dockerfile: jeśli jakiekolwiek kroki kompilacji zmienią dane w woluminie po jego zadeklarowaniu, te zmiany zostaną odrzucone.

...

  • Katalog hosta jest zadeklarowany w czasie wykonywania kontenera: katalog hosta (punkt podłączenia) jest ze swej natury zależny od hosta. Ma to na celu zachowanie przenośności obrazu, ponieważ nie można zagwarantować, że dany katalog hosta będzie dostępny na wszystkich hostach. Z tego powodu nie można zamontować katalogu hosta z poziomu pliku Dockerfile. VOLUME Instrukcja nie obsługuje określania host-dirparametru. Podczas tworzenia lub uruchamiania kontenera należy określić punkt montowania.

44

Aby lepiej zrozumieć volumeinstrukcję w pliku dockerfile, poznajmy typowe użycie wolumenu w oficjalnej implementacji pliku docker mysql.

VOLUME /var/lib/mysql

Źródła: https://github.com/docker-library/mysql/blob/3362baccb4352bcf0022014f67c1ec7e6808b8c5/8.0/Dockerfile

Jest /var/lib/mysqlto domyślna lokalizacja MySQL, w której są przechowywane pliki danych.

Kiedy uruchamiasz kontener testowy tylko w celach testowych, nie możesz określić jego punktu montowania, np

docker run mysql:8

wtedy instancja kontenera mysql użyje domyślnej ścieżki montowania określonej przez volumeinstrukcję w pliku dockerfile. woluminy są tworzone z bardzo długą, podobną do identyfikatora nazwą w katalogu głównym Dockera, nazywa się to woluminem „bez nazwy” lub „anonimowym”. W folderze podstawowego systemu hosta / var / lib / docker / volume.

/var/lib/docker/volumes/320752e0e70d1590e905b02d484c22689e69adcbd764a69e39b17bc330b984e4

Jest to bardzo wygodne do celów szybkiego testowania bez konieczności określania punktu montażu, ale nadal można uzyskać najlepszą wydajność, używając objętości do przechowywania danych, a nie warstwy kontenera.

Do użytku formalnego będziesz musiał określić ścieżkę montowania, używając nazwanego woluminu lub wiązania, np

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

Polecenie montuje katalog / my / own / datadir z podstawowego systemu hosta jako / var / lib / mysql wewnątrz kontenera. Katalog danych / my / own / datadir nie zostanie automatycznie usunięty, nawet kontener zostanie usunięty.

Wykorzystanie oficjalnego obrazu mysql (sprawdź sekcję „Gdzie przechowywać dane”):

Źródła: https://hub.docker.com/_/mysql/


2
Bardzo podoba mi się twoje wyjaśnienie.
LukaszTaraszka

1
Ale docker i tak zapisuje zmiany. Możesz także ustawić ścieżkę montowania, -vużyj go bez ustawiania głośności w pliku Dockerfile
Alex78191

To najlepsza odpowiedź, która wyjaśniła wszystko. Ostre i na temat z przykładami.
Kumar Manish

Przykład! jak odświeżające i jasne :) (serio)
Vano

40

VOLUMEPolecenie w Dockerfileto całkiem prawda, całkowicie konwencjonalny, absolutnie w porządku do użytku i nie jest przestarzałe w każdym razie. Po prostu muszę to zrozumieć.

Używamy go do wskazywania katalogów, w których aplikacja w kontenerze będzie dużo zapisywać. Nie używamy VOLUMEtylko dlatego, że chcemy współdzielić hosta i kontenera, jak plik konfiguracyjny.

Polecenie potrzebuje po prostu jednego parametru; ścieżka do folderu WORKDIRw kontenerze względem ustawienia, jeśli jest ustawiona. Następnie docker utworzy wolumen w swoim grafie (/ var / lib / docker) i zamontuje go w folderze w kontenerze. Teraz kontener będzie miał gdzie pisać z dużą wydajnością. Bez VOLUMEpolecenia prędkość zapisu do określonego folderu będzie bardzo niska, ponieważ teraz kontener używa swojej copy on writestrategii w samym kontenerze. Ta copy on writestrategia jest głównym powodem istnienia tomów.

Jeśli zamontujesz folder określony przez VOLUMEpolecenie, polecenie nigdy nie jest uruchamiane, ponieważ VOLUMEjest wykonywane tylko podczas uruchamiania kontenera, coś w rodzaju ENV.

Zasadniczo za pomocą VOLUMEpolecenia można uzyskać wydajność bez zewnętrznego montowania jakichkolwiek woluminów. Dane będą również zapisywane w przebiegach kontenerów bez żadnych zewnętrznych instalacji. Następnie, gdy będziesz gotowy, po prostu zamontuj coś na nim.

Kilka dobrych przykładów użycia:
- logi
- foldery tymczasowe

Niektóre złe przypadki użycia:
- pliki statyczne
- konfiguracje
- kod


2
Odnośnie dobrych i złych przykładów użycia, strona Dockera z „najlepszymi praktykami w pliku dockerfile” mówi: „Zachęcamy do używania VOLUME dla wszelkich zmiennych i / lub obsługiwanych przez użytkownika części obrazu”. Myślę, że konfiguracje tam są.
OmerSch

2
Nie ma VOLUMEnic złego w określaniu katalogów konfiguracji. Jednak gdy faktycznie zamontujesz konfigurację, będziesz musiał zamontować ją w tym katalogu i dlatego VOLUMEpolecenie nie działa. Dlatego nie ma sensu używać VOLUMEpolecenia na katalogu określonym dla konfiguracji. Również inicjalizacja wykresu wolumenu za pomocą pojedynczego statycznego pliku tylko do odczytu jest poważną przesadą. Więc trzymam się tego, co powiedziałem, nie ma potrzeby VOLUMEwydawania poleceń na konfiguracjach.
Mr Haven

Woluminy mogą mieć różne charakterystyki wydajności ze względu na szczegóły implementacji. Pliki danych bazy danych pasują do tego przypadku użycia, ale jaki byłby sens przechowywania danych obok (efemerycznego) przechowywania kontenerów? To znaczy przypisywanie istnienia woluminów wydajności jest nieprawidłowe.
André Werlang

5

W żadnym wypadku nie uważam użycia VOLUME za dobre, z wyjątkiem sytuacji, gdy tworzysz obraz dla siebie i nikt inny nie będzie z niego korzystał.

Negatywnie wpłynęło na mnie to, że VOLUME na obrazach podstawowych, które rozszerzyłem, i dowiedziałem się o problemie dopiero po uruchomieniu obrazu, na przykład wordpress, który deklaruje /var/www/htmlfolder jako VOLUME , a to oznacza, że ​​wszelkie pliki dodane lub zmienione etap kompilacji nie jest brany pod uwagę, a zmiany na żywo utrzymują się, nawet jeśli nie wiesz. Istnieje brzydkie obejście umożliwiające zdefiniowanie katalogu internetowego w innym miejscu, ale jest to po prostu złe rozwiązanie znacznie prostszego: po prostu usuń dyrektywę VOLUME.

Możesz łatwo osiągnąć wolumen za pomocą -vopcji, to nie tylko wyjaśnia, jakie będą woluminy kontenera (bez konieczności patrzenia na plik Dockerfile i nadrzędne Dockerfile), ale daje to również konsumentowi opcję używać głośności lub nie.

Złe jest również używanie WOLUMEN z następujących powodów, jak mówi ta odpowiedź :

Jednak instrukcja VOLUME ma swoją cenę.

  • Użytkownicy mogą nie być świadomi tworzenia nienazwanych woluminów i nadal zajmować miejsce w pamięci na swoim hoście platformy Docker po usunięciu kontenerów.
  • Nie ma możliwości usunięcia woluminu zadeklarowanego w pliku Dockerfile. Obrazy podrzędne nie mogą dodawać danych do ścieżek, w których istnieją woluminy.

Ta ostatnia kwestia powoduje takie problemy.

Opcja cofnięcia deklaracji woluminu byłaby pomocna, ale tylko wtedy, gdy znasz woluminy zdefiniowane w pliku dockerfile, który wygenerował obraz (i nadrzędne pliki dockerfile!). Ponadto w nowszych wersjach pliku Dockerfile można dodać VOLUME i nieoczekiwanie zepsuć sytuację użytkowników obrazu.

Kolejne dobre wyjaśnienie ( o obrazie wyroczni posiadającym VOLUME , który został usunięty ): https://github.com/oracle/docker-images/issues/640#issuecomment-412647328

Więcej przypadków, w których VOLUME zepsuł dla ludzi:

Prośba przyciąganie dodać opcje resetowania Właściwości obrazu nadrzędnego (włączając objętość), został zamknięty i jest omówione są tutaj (i można zobaczyć kilka przypadków z ludzi dotkniętych negatywnie z powodu objętości określonych w dockerfiles), który ma komentarz z dobrym wyjaśnienie w stosunku do VOLUME:

Używanie VOLUME w pliku Dockerfile jest bezwartościowe. Jeśli użytkownik potrzebuje trwałości, z pewnością zapewni mapowanie woluminu podczas uruchamiania określonego kontenera. Bardzo trudno było wyśledzić, że mój problem z niemożnością ustawienia własności katalogu (/ var / lib / influxdb) był spowodowany deklaracją VOLUME w pliku Dockerfile InfluxDB. Bez opcji typu UNVOLUME lub całkowitego pozbycia się jej nie mogę zmienić niczego związanego z określonym folderem. Jest to mniej niż idealne, zwłaszcza jeśli jesteś świadomy bezpieczeństwa i chcesz określić określony UID, obraz powinien zostać uruchomiony, ponieważ w celu uniknięcia przypadkowego użytkownika, z większą liczbą uprawnień niż to konieczne, uruchamiającym oprogramowanie na hoście.

Jedyną dobrą rzeczą, jaką widzę w VOLUME, jest dokumentacja i uznałbym ją za dobrą, gdyby tylko to zrobiła (bez żadnych skutków ubocznych).

TL; DR

Uważam, że najlepszym sposobem wykorzystania VOLUME jest wycofanie go.


dziękuję za poświęcenie czasu i szczegółową odpowiedź
Vano
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.