Compare commits
	
		
			No commits in common. 'main' and 'practice4' have entirely different histories. 
		
	
	
		
	| @ -1,9 +0,0 @@ | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: default | ||||
| 
 | ||||
| steps: | ||||
| - name: test | ||||
|   image: nixos/nix:master | ||||
|   commands: | ||||
|   - echo 522 | ||||
| @ -1,6 +0,0 @@ | ||||
| ### Что здесь лежит? | ||||
| 
 | ||||
| В файле [git instruction](./git_instruction.md) лежат | ||||
| инструкции, которые нужно проделать в качестве первичной практики | ||||
| по **git**. | ||||
| 
 | ||||
| @ -1,44 +0,0 @@ | ||||
| ## Что нужно сделать? | ||||
| 
 | ||||
| 0. Зайти на сайт: [cs-sh.xyz]. | ||||
| 1. Зарегистрироваться, если ещё не. | ||||
| 2. Создать репозиторий, называть можно как угодно. | ||||
| 3. Взять HTTPS ссылку к репозиторию (далее `<url>`). | ||||
| 4. Склонировать репозиторий к себе на машину по этой ссылке: | ||||
| ```ssh | ||||
| git clone <url> | ||||
| ``` | ||||
| 5. Открыть склонированный репозиторий как обычную директорию _любым_ привычным | ||||
| способом: | ||||
|   - в терминале с помощью команды `cd`, `pwd` также может помочь; | ||||
|   - в Windows с помощью File Explorer, в других ОС -- с помощью его аналогов; | ||||
| 
 | ||||
| 6. Создать файл `README.md`, если он ещё не был создан. | ||||
| 
 | ||||
| 7. Изменить файл `README.md`, наполнить каким-нибудь содержимым. | ||||
| 
 | ||||
| 8. Создать коммит с **содержательным** сообщением. | ||||
| 
 | ||||
| Для этого добавить файлы в Staging: | ||||
| ```ssh | ||||
| git add README.md | ||||
| ``` | ||||
| и создать коммит: | ||||
| ``` | ||||
| # git commit -m "some mess" | ||||
| # лучше | ||||
| git commit | ||||
| ``` | ||||
| 9. Запушить содержимое ветки в ветку `main`. | ||||
| ```sh | ||||
| git push origin main | ||||
| ``` | ||||
| 10. "Отпочковать" новую ветку: | ||||
| ``` | ||||
| git checkout -b name_of_branch | ||||
| ``` | ||||
| 11. Изменить или добавить какие-нибудь файлы, создать коммит. | ||||
| 12. Запушить изменения на [cs-sh.xyz] в **свой** (не в данный!) репозиторий в ветку `name_of_branch`. | ||||
| 13. Создать Pull Request (Запрос на слияние) в **СВОЙ** репозиторий. | ||||
| 
 | ||||
| [cs-sh.xyz]: https://cs-sh.xyz | ||||
| @ -1,6 +0,0 @@ | ||||
| ### Что здесь лежит? | ||||
| 
 | ||||
| В файле [sh instruction](./sh_instruction.md) лежат | ||||
| инструкции, которые нужно проделать в качестве первичной практики | ||||
| по **shell**. | ||||
| 
 | ||||
| @ -1,43 +0,0 @@ | ||||
| ## Что делать? | ||||
| 
 | ||||
| 1. Открыть терминал. | ||||
| 
 | ||||
| 2. Открыть и полистать man-pages: | ||||
|   - `man` | ||||
|   - `pwd` | ||||
|   - `cd` | ||||
|   - `ls` | ||||
|   - `mkdir` | ||||
|   - `rm` | ||||
|   - `rmdir` | ||||
|   - `cat` | ||||
|   - `bash` | ||||
|   - `grep` | ||||
| 
 | ||||
| Что делать, если **no manual entry found** | ||||
| ```bash | ||||
| $ sudo apt update | ||||
| $ sudo apt install manpages-posix | ||||
| ``` | ||||
| 
 | ||||
| 3. Не забывайте, что `man` откроет пагинатор вроде `less` (или `most`, как у меня); | ||||
| убедитесь, что понимаете как им пользоваться: | ||||
|   - вызовите помощь с помощью клавиши `h`; | ||||
|   - найдите какое-нибудь вхождение с помощью `/`,`?` и клавиш `n`/`N`; | ||||
|   - поймите как пролистывать целый экран/половину экрана вверх-вниз; | ||||
|   - выясните как перейти в начало/конец файла; | ||||
| 
 | ||||
| 4. ознакомиться с утилитой `help`, начать можно с вызова `help` без аргументов, | ||||
| далее -- повызывать `help` для различных встроенных команд, например, | ||||
| `help set`. | ||||
| 
 | ||||
| 5. Открыть `info coreutils` | ||||
| 
 | ||||
| Попробовать попользоваться: | ||||
|   - потыкать клавиши вниз-вверх; | ||||
|   - потыкать клавиши `[`,`]`,`p`,`n`; | ||||
|   - понять, какие действия они делают; | ||||
|   - научиться вызывать помощь; | ||||
| 
 | ||||
| Обязательно офигеть, закрыть, открыть info-страницы в браузере по ссылке с | ||||
| cscwiki. | ||||
| @ -1,27 +0,0 @@ | ||||
| ### Что здесь лежит? | ||||
| 
 | ||||
| Только README.md, больше ничего | ||||
| 
 | ||||
| ### Задания на практику | ||||
| 
 | ||||
| #### 1. Удваиватель | ||||
| 
 | ||||
| Написать команду, копирующую содержимое файла `data` в конец этого же файла. | ||||
| 
 | ||||
| Примечания: | ||||
|  * решение на полный балл не должно использовать временные файлы; | ||||
|  * файл может содержать бинарные данные; | ||||
|  * проверьте, что если изначальный размер файла был n байт, то размер результирующего файла -- 2 * n байт | ||||
|  * проверьте свое решение на больших (> 1 MB) файлах; | ||||
|   | ||||
| #### 2. Числа Фибоначчи | ||||
| 
 | ||||
| Определим числа Фибоначчи следующим образом: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| Вам нужно написать скрипт, который будет считывать из `stdin` | ||||
| число `n` и выводить в `stdout` `n`-ое число Фибоначчи. | ||||
| 
 | ||||
| Примечания: | ||||
| * Время работы функции -- `O(n)`. | ||||
| @ -1,37 +0,0 @@ | ||||
| data A = True' | False' | ||||
|   deriving Show | ||||
| 
 | ||||
| foo :: A -> Integer | ||||
| foo True'  = 1 | ||||
| foo False' = 0 | ||||
| 
 | ||||
| data P3 = P Bool A -- P (Bool x A) | ||||
| 
 | ||||
| data BigP = P1 Bool A | P2 Integer | ||||
|   deriving Show | ||||
| 
 | ||||
| bar :: BigP -> Integer | ||||
| bar (P1 _ _) = 1 | ||||
| bar (P2 _) = 2 | ||||
| 
 | ||||
| int_plus_3 :: Integer -> Integer | ||||
| int_plus_3 n = n + 3 | ||||
| 
 | ||||
| -- examples | ||||
| comp :: BigP -> Integer | ||||
| comp = int_plus_3 . bar | ||||
| -- same as: int_plus_3 $ bar $ P2 10 | ||||
| 
 | ||||
| double_arg :: Integer -> Integer -> Integer | ||||
| double_arg a b = a + b | ||||
| 
 | ||||
| partially_applied :: Integer -> Integer | ||||
| partially_applied = double_arg 7 | ||||
| 
 | ||||
| 
 | ||||
| func_arg :: (Integer -> Integer) -> Integer -> Integer | ||||
| func_arg f b = f b | ||||
| 
 | ||||
| 
 | ||||
| main :: IO () | ||||
| main = putStrLn "Hello world" | ||||
| @ -1,35 +0,0 @@ | ||||
| 
 | ||||
| type Name = String | ||||
| type Table = [ (Name, Name) ] | ||||
| 
 | ||||
| fathers :: Table | ||||
| fathers = [ | ||||
|   ("a", "d"), | ||||
|   ("b", "r") | ||||
|   ] | ||||
| 
 | ||||
| head' :: [a] -> a | ||||
| head' (x:xs) = x | ||||
| 
 | ||||
| 
 | ||||
| helper :: Integer -> [a] -> Integer | ||||
| helper acc (x:xs) = helper (acc + 1) xs | ||||
| helper acc ([]) = acc | ||||
| 
 | ||||
| len' :: [a] -> Integer | ||||
| len' xs = helper 0 xs | ||||
| 
 | ||||
| 
 | ||||
| inv :: [a] -> [a] -> [a] | ||||
| inv acc (x:xs) = inv (x : acc) xs | ||||
| inv acc [] = acc | ||||
| 
 | ||||
| rev :: [a] -> [a] | ||||
| rev xs = inv [] xs | ||||
| 
 | ||||
| -- 1 : 2 : 3 : [] | ||||
| -- 3 : 2 : 1 : [] | ||||
| 
 | ||||
| 
 | ||||
| getF :: Name -> Maybe Name | ||||
| getF n = lookup n fathers | ||||
| @ -1,21 +0,0 @@ | ||||
| ### Задаание 1 | ||||
| 
 | ||||
| Используя библиотеку `time`, написать декоратор `@bench(n)`, | ||||
| который меняет функцию так, чтобы при каждом её вызове она | ||||
| вычислялась не один раз, а `n` раз, при этом необходимо выводить: | ||||
| 
 | ||||
| - имя функции; | ||||
| - аргументы; | ||||
| - средняя время работы за `n` запусков; | ||||
| 
 | ||||
| ```python | ||||
| 
 | ||||
| @bench(50) | ||||
| def foo(a: int, b: int): | ||||
|     ... | ||||
| 
 | ||||
| 
 | ||||
| >>> foo(5, 5) | ||||
| <... foo> (5, 5) {} | ||||
| Mean execution time on <N> calls: ???ns | ||||
| ``` | ||||
| @ -1 +0,0 @@ | ||||
| 
 | ||||
| @ -1,9 +0,0 @@ | ||||
| def adder(): | ||||
|     n = 0 | ||||
| 
 | ||||
|     def add(): | ||||
|         nonlocal n | ||||
|         n += 1 | ||||
|         return n | ||||
| 
 | ||||
|     return add | ||||
| @ -1,14 +0,0 @@ | ||||
| def debug_call(f): | ||||
|     def inner(*args, **kwargs): | ||||
|         print(f, args, kwargs) | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| # @debug_call | ||||
| def foo(a, b): | ||||
|     return a + b | ||||
| 
 | ||||
| 
 | ||||
| foo = debug_call(foo) | ||||
| @ -1,17 +0,0 @@ | ||||
| def deco(f): | ||||
|     def inner(*args, **kwargs): | ||||
|         print(f) | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| # @deco | ||||
| def foo(): | ||||
|     """ | ||||
|     Foo is just a function. | ||||
|     """ | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| foo = deco(foo) | ||||
| @ -1,8 +0,0 @@ | ||||
| def deco_ch(f): | ||||
|     f.jjjjj = 10 | ||||
|     return f | ||||
| 
 | ||||
| 
 | ||||
| @deco_ch | ||||
| def foo(): | ||||
|     print(foo.jjjjj) | ||||
| @ -1,77 +0,0 @@ | ||||
| # first attempt | ||||
| def deco_first(f): | ||||
|     def inner(*args, **kwargs): | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     inner.__name__ = f.__name__ | ||||
|     inner.__doc__ = f.__doc__ | ||||
|     inner.__qualname__ = f.__qualname__ | ||||
| 
 | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| # second attempt | ||||
| from functools import update_wrapper | ||||
| 
 | ||||
| 
 | ||||
| def deco_second(f): | ||||
|     def inner(*args, **kwargs): | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     update_wrapper(inner, f) | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| # third attempt | ||||
| 
 | ||||
| 
 | ||||
| def my_wraps(original): | ||||
|     def deco(wrapper): | ||||
|         update_wrapper(wrapper, original) | ||||
|         return wrapper | ||||
| 
 | ||||
|     return deco | ||||
| 
 | ||||
| 
 | ||||
| from functools import partial | ||||
| 
 | ||||
| 
 | ||||
| def partial_wraps(original): | ||||
|     return partial(update_wrapper, wrapped=original) | ||||
| 
 | ||||
| 
 | ||||
| # def deco_third(f): | ||||
| #     def inner(*args, **kwargs): | ||||
| #         return f(*args, **kwargs) | ||||
| # | ||||
| #     deco = my_wraps(f) | ||||
| #     inner = deco(inner) | ||||
| #     return inner | ||||
| 
 | ||||
| 
 | ||||
| def deco_third(f): | ||||
|     @partial_wraps(f) | ||||
|     def inner(*args, **kwargs): | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| # third attempt | ||||
| from functools import wraps | ||||
| 
 | ||||
| 
 | ||||
| def deco_fourth(f): | ||||
|     @wraps(f) | ||||
|     def inner(*args, **kwargs): | ||||
|         return f(*args, **kwargs) | ||||
| 
 | ||||
|     return inner | ||||
| 
 | ||||
| 
 | ||||
| @deco_third | ||||
| def foo(a, b, c): | ||||
|     """ | ||||
|     Foo function docstring | ||||
|     """ | ||||
|     return a + b + c | ||||
| @ -1,8 +0,0 @@ | ||||
| from functools import partial | ||||
| 
 | ||||
| 
 | ||||
| def foo(a, b, new_name): | ||||
|     return new_name * (b + a) | ||||
| 
 | ||||
| 
 | ||||
| bar = partial(foo, c=500) | ||||
| @ -1,33 +0,0 @@ | ||||
| def adder(): | ||||
|     n = 0 | ||||
| 
 | ||||
|     def add(): | ||||
|         nonlocal n | ||||
|         n += 1 | ||||
|         return n | ||||
| 
 | ||||
|     return add | ||||
| 
 | ||||
| 
 | ||||
| def retry(n: int = 5): | ||||
|     def deco(f): | ||||
|         def inner(*args, **kwargs): | ||||
|             for _ in range(n): | ||||
|                 f(*args, **kwargs) | ||||
| 
 | ||||
|         return inner | ||||
| 
 | ||||
|     return deco | ||||
| 
 | ||||
| 
 | ||||
| # @retry(5) | ||||
| def foo(): | ||||
|     print("Hello world!") | ||||
| 
 | ||||
| 
 | ||||
| foo = (retry(5))(foo) | ||||
| 
 | ||||
| 
 | ||||
| @retry(15) | ||||
| def bar(): | ||||
|     print("Bar") | ||||
| @ -1,11 +0,0 @@ | ||||
| ### Практика про классы в питоне | ||||
| 
 | ||||
| Что здесь есть полезного? | ||||
| - [definition](./definition) -- определения классов; | ||||
| - [descriptor](./descriptor) -- про модификаторы и как использовать `@property`; | ||||
| - [magic](./magic) -- куча примеров магических методов; | ||||
| - [mro](./mro) -- method resolution order или примеры про плату за сомнительные решения; | ||||
| - [class-deco](./class-deco) -- как использовать класс в качестве декоратора; | ||||
| - [dataclasses](./dataclasses) -- датаклассы (классы без методов, но только с | ||||
| данными); | ||||
| - [mixins](./mixins) -- пример паттерна mixin; | ||||
| @ -1,15 +0,0 @@ | ||||
| class ClassDeco: | ||||
|     def __init__(self, f): | ||||
|         self.f = f | ||||
| 
 | ||||
|     def __call__(self, *args, **kwargs): | ||||
|         print(self.f, args, kwargs) | ||||
|         return self.f(*args, **kwargs) | ||||
| 
 | ||||
| 
 | ||||
| @ClassDeco | ||||
| def foo(): | ||||
|     return 45 | ||||
| 
 | ||||
| 
 | ||||
| # foo = ClassDeco(foo) | ||||
| @ -1,28 +0,0 @@ | ||||
| from dataclasses import dataclass | ||||
| from typing import Union | ||||
| 
 | ||||
| 
 | ||||
| #  def dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, | ||||
| #                unsafe_hash=False, frozen=False): | ||||
| #      """Returns the same class as was passed in, with dunder methods | ||||
| #      added based on the fields defined in the class. | ||||
| # | ||||
| #      Examines PEP 526 __annotations__ to determine fields. | ||||
| # | ||||
| #      If init is true, an __init__() method is added to the class. If | ||||
| #      repr is true, a __repr__() method is added. If order is true, rich | ||||
| #      comparison dunder methods are added. If unsafe_hash is true, a | ||||
| #      __hash__() method function is added. If frozen is true, fields may | ||||
| #      not be assigned to after instance creation. | ||||
| #      """ | ||||
| 
 | ||||
| 
 | ||||
| @dataclass(frozen=True) | ||||
| class A: | ||||
|     a: int | ||||
|     b: bool | ||||
|     c: str = "asd" | ||||
|     f: Union[int, bool, str] = 0 | ||||
| 
 | ||||
|     def foo(self): | ||||
|         print(self) | ||||
| @ -1,17 +0,0 @@ | ||||
| class A: | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class C: | ||||
|     a, b = 1, 1 | ||||
|     for i in range(15): | ||||
|         a, b = b, a + b | ||||
| 
 | ||||
|     print(a) | ||||
|     f = lambda self, a: a | ||||
|     g = lambda self, a: self.f(a) | ||||
| 
 | ||||
|     def __init__(self, x, y): | ||||
|         print(self) | ||||
|         self.x = x | ||||
|         self.y = y | ||||
| @ -1,7 +0,0 @@ | ||||
| class A: | ||||
|     n = 5 | ||||
|     e = 50 | ||||
|     f = lambda self: 42 | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self.a = 1000 | ||||
| @ -1,12 +0,0 @@ | ||||
| class C: | ||||
|     f = 50 | ||||
| 
 | ||||
|     a, b = 1, 1 | ||||
|     for _ in range(10): | ||||
|         a, b = b, a + b | ||||
|     print(a, b) | ||||
| 
 | ||||
|     def __init__(self, a, b): | ||||
|         print(self) | ||||
|         self.a = a | ||||
|         self.b = b | ||||
| @ -1,14 +0,0 @@ | ||||
| # class Base(object) | ||||
| class Base: | ||||
|     def foo(self): | ||||
|         return 42 | ||||
| 
 | ||||
| 
 | ||||
| class NotBase: | ||||
|     def foo(self): | ||||
|         return 43 | ||||
| 
 | ||||
| 
 | ||||
| class A(NotBase, Base): | ||||
|     def bar(self): | ||||
|         return 41 | ||||
| @ -1,17 +0,0 @@ | ||||
| class A: | ||||
|     def __init__(self, a, b): | ||||
|         self._a = a | ||||
|         self.__b = b | ||||
| 
 | ||||
|     @property | ||||
|     def super_duper_var(self): | ||||
|         print(self) | ||||
|         return self.__b | ||||
| 
 | ||||
|     @super_duper_var.setter | ||||
|     def super_duper_var(self, value): | ||||
|         self.__b = value | ||||
| 
 | ||||
|     # @super_duper_var.deleter | ||||
|     # def super_duper_var(self): | ||||
|     #     pass | ||||
| @ -1,8 +0,0 @@ | ||||
| from abc import ABC | ||||
| 
 | ||||
| 
 | ||||
| class MyHashable(ABC): | ||||
|     @classmethod | ||||
|     def __subclasshook__(cls, sbcls): | ||||
|         hash_func = getattr(sbcls, "__hash__", None) | ||||
|         return hash_func is not None | ||||
| @ -1,3 +0,0 @@ | ||||
| class AlwaysHaveField: | ||||
|     def __getattr__(self, value): | ||||
|         return value | ||||
| @ -1,23 +0,0 @@ | ||||
| class Iterator: | ||||
|     def __init__(self, origin_list): | ||||
|         self._ref = origin_list | ||||
|         self.n = -1 | ||||
| 
 | ||||
|     def __next__(self): | ||||
|         if self.n < len(self._ref) - 1: | ||||
|             self.n += 1 | ||||
|             return self._ref[self.n] | ||||
|         raise StopIteration() | ||||
| 
 | ||||
| 
 | ||||
| class A: | ||||
|     def __init__(self): | ||||
|         self.a = [1, 2, 3, 4] | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return Iterator(self.a) | ||||
| 
 | ||||
| 
 | ||||
| a = A() | ||||
| for el in a: | ||||
|     print(el) | ||||
| @ -1,11 +0,0 @@ | ||||
| class A: | ||||
|     def __init__(self): | ||||
|         self.a = [1, 2, 3, 4] | ||||
| 
 | ||||
|     def __getitem__(self, ind): | ||||
|         return self.a[ind] | ||||
| 
 | ||||
| 
 | ||||
| a = A() | ||||
| for el in a: | ||||
|     print(el) | ||||
| @ -1,20 +0,0 @@ | ||||
| class MADATA: | ||||
|     def __init__( | ||||
|         self, | ||||
|         a: int, | ||||
|         b: int, | ||||
|         c: int, | ||||
|     ): | ||||
|         self.a = a | ||||
|         self.b = b | ||||
|         self.c = c | ||||
| 
 | ||||
|     @property | ||||
|     def _tup_view(self): | ||||
|         return self.a, self.b, self.c | ||||
| 
 | ||||
|     def __hash__(self): | ||||
|         return hash(self._tup_view) | ||||
| 
 | ||||
|     def __eq__(self, o): | ||||
|         return isinstance(o, type(self)) and o._tup_view == self._tup_view | ||||
| @ -1,11 +0,0 @@ | ||||
| class A: | ||||
|     def __init__(self): | ||||
|         self.a = 1 | ||||
|         self.b = 2 | ||||
| 
 | ||||
|     def foo(self): | ||||
|         print("Hello") | ||||
| 
 | ||||
|     def __getattr__(self, name): | ||||
|         print(f"Called __getattr__ with arg: {name}") | ||||
|         return name | ||||
| @ -1,6 +0,0 @@ | ||||
| class DF: | ||||
|     def __getitem__(self, key): | ||||
|         return key | ||||
| 
 | ||||
|     def __setitem__(self, key, value): | ||||
|         print(key, value) | ||||
| @ -1,14 +0,0 @@ | ||||
| class GirlsMixin: | ||||
|     def hooray(self): | ||||
|         print("Hoooray") | ||||
| 
 | ||||
| 
 | ||||
| class CatMixin: | ||||
|     def feed(self, hp): | ||||
|         print("Meow") | ||||
| 
 | ||||
| 
 | ||||
| class CatGirls(GirlsMixin, CatMixin): | ||||
|     def main(self): | ||||
|         super().feed(50) | ||||
|         super().hooray() | ||||
| @ -1,34 +0,0 @@ | ||||
| class Base: | ||||
|     def foo(self): | ||||
|         print("Base") | ||||
| 
 | ||||
| 
 | ||||
| class A(Base): | ||||
|     def foo(self): | ||||
|         print("A") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class B(Base): | ||||
|     def foo(self): | ||||
|         print("B") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class C(A, B): | ||||
|     def foo(self): | ||||
|         print("C") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class D(B, A): | ||||
|     def foo(self): | ||||
|         print("D") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class E(C, D): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| print("HEELLO") | ||||
| @ -1,21 +0,0 @@ | ||||
| class Base: | ||||
|     def __init__(self, a): | ||||
|         self.x = a | ||||
| 
 | ||||
| 
 | ||||
| class A(Base): | ||||
|     def __init__(self): | ||||
|         super().__init__(5) | ||||
| 
 | ||||
| 
 | ||||
| class B(Base): | ||||
|     def __init__(self, x): | ||||
|         super().__init__(x) | ||||
| 
 | ||||
| 
 | ||||
| class C(A, B): | ||||
|     ... | ||||
| 
 | ||||
| 
 | ||||
| class D(B, A): | ||||
|     ... | ||||
| @ -1,24 +0,0 @@ | ||||
| class Base: | ||||
|     def foo(self): | ||||
|         print("Base") | ||||
| 
 | ||||
| 
 | ||||
| class A(Base): | ||||
|     def foo(self): | ||||
|         print("A") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class B(Base): | ||||
|     def foo(self): | ||||
|         print("B") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| class C(A, B): | ||||
|     def foo(self): | ||||
|         print("C") | ||||
|         super().foo() | ||||
| 
 | ||||
| 
 | ||||
| print("HEELLO") | ||||
| @ -1,2 +0,0 @@ | ||||
| a = {k: k * 379 for k in range(10)} | ||||
| print(a) | ||||
| @ -1,9 +0,0 @@ | ||||
| els = ["a", "b", "c"] | ||||
| 
 | ||||
| 
 | ||||
| def _enum(els): | ||||
|     return zip(range(len(els)), els) | ||||
| 
 | ||||
| 
 | ||||
| for idx, el in _enum(els): | ||||
|     print(idx, el) | ||||
| @ -1,36 +0,0 @@ | ||||
| class IterableStack: | ||||
|     def __init__(self): | ||||
|         self._lst = [1, 2, 3, 4, 5] | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return ReverseIterator(self._lst) | ||||
| 
 | ||||
| 
 | ||||
| class ReverseIterator: | ||||
|     def __init__(self, _lst): | ||||
|         self._lst = _lst | ||||
|         self.position = len(_lst) | ||||
| 
 | ||||
|     def __next__(self): | ||||
|         self.position -= 1 | ||||
|         if self.position < 0: | ||||
|             raise StopIteration() | ||||
| 
 | ||||
|         return self._lst[self.position] | ||||
| 
 | ||||
| 
 | ||||
| # container = IterableStack() | ||||
| # for v in container: | ||||
| #     print(v) | ||||
| 
 | ||||
| container = IterableStack() | ||||
| it_container = iter(container)  # container.__iter__() | ||||
| while True: | ||||
|     try: | ||||
|         v = next(it_container)  # it_container.__next__() | ||||
| 
 | ||||
|         # тело фора | ||||
|         print(v) | ||||
|         # конец тела фора | ||||
|     except StopIteration: | ||||
|         break | ||||
| @ -1,9 +0,0 @@ | ||||
| def local_gen(): | ||||
|     n = 0 | ||||
| 
 | ||||
|     def next(): | ||||
|         nonlocal n | ||||
|         n += 1 | ||||
|         return n | ||||
| 
 | ||||
|     return next | ||||
| @ -1,17 +0,0 @@ | ||||
| def _range(start, stop): | ||||
|     assert start < stop | ||||
| 
 | ||||
|     def inside_gen(): | ||||
|         nonlocal start | ||||
|         while start < stop: | ||||
|             print("Going to return: ", start) | ||||
|             yield start | ||||
|             start += 1 | ||||
| 
 | ||||
|         print("here") | ||||
| 
 | ||||
|     return inside_gen() | ||||
| 
 | ||||
| 
 | ||||
| # for el in _range(0, 10): | ||||
| #     print(el) | ||||
| @ -1,20 +0,0 @@ | ||||
| from typing import List | ||||
| 
 | ||||
| 
 | ||||
| def load_files(paths: List[str]): | ||||
|     for path in paths: | ||||
|         with open(path) as f: | ||||
|             yield f.readlines() | ||||
| 
 | ||||
| 
 | ||||
| paths = [ | ||||
|     "dict_gen.py", | ||||
|     "enum.py", | ||||
|     "for_loop.py", | ||||
|     "gen.py", | ||||
|     "lzy_load.py", | ||||
| ] | ||||
| 
 | ||||
| for cont in load_files(paths): | ||||
|     # code working with content | ||||
|     print(cont) | ||||
| @ -1,13 +0,0 @@ | ||||
| # Docker Tutorial | ||||
| 
 | ||||
| 
 | ||||
| * [Prerequisites](prerequisites) | ||||
| * [Useful external links](links) | ||||
| * [Summary of commands](summary) | ||||
| * [Lesson 1: Docker basics and running a container](lesson01) | ||||
| * [Lesson 2: Introduction to Docker image builds](lesson02) | ||||
| * [Lesson 3: Image layers](lesson03) | ||||
| * [Lesson 4: Persisting data](lesson04) | ||||
| * [Lesson 5: Network access](lesson05) | ||||
| * [Lesson 6: Environment variables and configuration](lesson06) | ||||
| * [Good Docker practices](practices) | ||||
| @ -1,100 +0,0 @@ | ||||
| # Lesson 1: Docker basics and running a container | ||||
| 
 | ||||
| 1. Download the `busybox` Docker image from Docker Hub: | ||||
| 
 | ||||
|         $ docker images | ||||
|         $ docker pull busybox | ||||
|         $ docker images | ||||
| 
 | ||||
| 1. What do the columns mean? The first two are `REPOSITORY` and | ||||
| `TAG`. Think of these as a way to name-space docker images. The | ||||
| `REPOSITORY` is the name for a group of related repositories. For the case | ||||
| of `busybox` the repository name is `busybox`. The second part of the | ||||
| namespace is `TAG` and is separated from `REPOSITORY` with a `:` | ||||
| (colon). If not explictly given, the tag defaults to `latest`. | ||||
| 
 | ||||
| 1. We will discuss tagging and the other columns later. | ||||
| 
 | ||||
| 1. Let's run busybox. | ||||
| 
 | ||||
|         $ docker run busybox /bin/sh -c "echo 'Hello' | md5sum" | ||||
|         09f7e02f1290be211da707a266f153b3  - | ||||
| 
 | ||||
| 1. What _is_ a docker container? | ||||
| 
 | ||||
|     > A container is a standard unit of software that packages up code and all | ||||
|     > its dependencies so the application runs quickly and reliably from one | ||||
|     > computing environment to another. (From https://www.docker.com) | ||||
| 
 | ||||
| 1. At heart a Docker container is a set of processes running in a | ||||
| ["namespace"](https://en.wikipedia.org/wiki/Linux_namespaces). These | ||||
| namespaces isolate the processes from the other processes running on the | ||||
| server. You can think of all this as a light-weight virtual machine. | ||||
| 
 | ||||
| 1. List the namespace of a running docker container (`lsns` is a Linux | ||||
| command): | ||||
| 
 | ||||
|         $ docker run busybox /bin/sh -c "sleep 1000" & | ||||
|         root> lsns  (must run as root to see the namespaces) | ||||
| 
 | ||||
| 1. Because Docker containers are just processes running on an existing | ||||
| server inside of a namespace, Docker images use the server's kernel. Thus, | ||||
| only functionality supported by the underlying kernel will work in a | ||||
| Docker container. | ||||
| 
 | ||||
| 1. Docker containers also use ["control | ||||
| groups"](https://en.wikipedia.org/wiki/Cgroups) which allow the host | ||||
| operating system to put limits on the resources used by the running Docker | ||||
| container. Limits can be placed on CPU, memory use, and I/O. | ||||
| 
 | ||||
|         # Limit docker to 10MB an use up all the memory | ||||
|         # (idea from https://unix.stackexchange.com/questions/99334/how-to-fill-90-of-the-free-memory) | ||||
|         $ docker run -m=10m busybox /bin/sh -c "cat /dev/zero | head -c 1m | tail" | ||||
|         $ docker run -m=10m busybox /bin/sh -c "cat /dev/zero | head -c 20m | tail" | ||||
| 
 | ||||
| 1. Unless you use an extra option the containers that you run will stick | ||||
| around. To see this, use the `docker ps` command: | ||||
| 
 | ||||
|         $ docker ps --all | ||||
|         $ docker ps -a    # (-a is the same as --all) | ||||
| 
 | ||||
| 1. Note that the names of the containers are random words. To give your | ||||
| container a name, use the `--name` command: | ||||
| 
 | ||||
|         $ docker run --name=fuzzle busybox /bin/sh -c "echo 'Hello' | md5sum" | ||||
|         $ docker ps -a | grep fuzzle | ||||
| 
 | ||||
| 1. To remove one of these left over containers use `docker rm`: | ||||
| 
 | ||||
|         $ docker ps -a | grep fuzzle | ||||
|         $ docker rm fuzzle | ||||
|         $ docker ps -a | grep fuzzle | ||||
| 
 | ||||
| 1. To remove all stopped containers use `docker container prune`: | ||||
| 
 | ||||
|         $ docker ps -a | ||||
|         $ docker container prune | ||||
|         $ docker ps -a | ||||
| 
 | ||||
| 1. To avoid the whole stopped container messiness, tell Docker to remove | ||||
| the container once it exits with teh `--rm` option: | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle busybox /bin/sh -c "echo 'Hello' | md5sum" | ||||
|         $ docker ps -a | grep fuzzle | ||||
| 
 | ||||
| 1. You can "login" to a running docker container: | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle busybox /bin/sh -c "sleep 10000" & | ||||
|         $ docker ps -a | grep fuzzle | ||||
|         $ docker exec -ti fuzzle /bin/sh | ||||
|         / # # You are "inside" the running container; run some commands | ||||
|         / # ps -eaf | ||||
|         / # df -h | ||||
| 
 | ||||
| 1. The `-ti` options tell Docker that you want to allocate a pseudo-TTY | ||||
| and use "interactive mode". *Warning:* logging into a running container is | ||||
| not exactly like ssh'ing into a server: some commands that depend on the | ||||
| terminal type may not work like you expect (e.g., editors, pagers, etc.) | ||||
| 
 | ||||
| 1. Being able to login to a running container is **very** useful when debugging | ||||
| your Docker builds. | ||||
| @ -1,7 +0,0 @@ | ||||
| # Dockerfile | ||||
| FROM debian:buster-slim | ||||
| LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
| ADD run.sh /root/run.sh | ||||
| RUN chmod a+x /root/run.sh | ||||
| CMD /root/run.sh | ||||
| @ -1,97 +0,0 @@ | ||||
| # Lesson 2: Introduction to Docker image builds | ||||
| 
 | ||||
| 1. Clone this git repository. | ||||
| 
 | ||||
| 1. Change directory into `lesson02`: | ||||
| 
 | ||||
|         $ cd lesson02 | ||||
| 
 | ||||
| 1. Why are containers useful? What are their advantages over a | ||||
| traditional server? | ||||
| 
 | ||||
|    - containers are light | ||||
|    - containers are portable | ||||
|    - containers are isolated | ||||
|    - containers can be run "immutably" | ||||
|    - containers are built hierarchically | ||||
|    - developers can create applications without a full server-stack | ||||
| 
 | ||||
| 1. What are some limitations of containers? | ||||
| 
 | ||||
|    - interaction is more difficult for multiple containers than for | ||||
|      multiple server process (although Kubernetes helps) | ||||
|    - some overhead so not quite as fast as "bare-metal" processes | ||||
|    - there are several decades worth of server administration best practices | ||||
|      and tools but only a few years for containers | ||||
|    - not good for large tightly-integrated applications (e.g., Oracle database) | ||||
| 
 | ||||
| 1. Question: what is the difference between an "image" and a "container"? | ||||
| (See also [this Stackoverflow | ||||
| question](https://stackoverflow.com/questions/23735149/what-is-the-difference-between-a-docker-image-and-a-container)). | ||||
| 
 | ||||
| 1. Most Docker images are build on top of existing "base" images. These | ||||
| base containers are usually hosted in Docker Hub. For example, all Debian | ||||
| releases come as Docker images; see https://hub.docker.com/_/debian for a | ||||
| list of the base Debian Docker images. | ||||
| 
 | ||||
| 1. Let's build a "Hello, world." Docker image. We will build it on a | ||||
| Debian buster base. First, pull the Docker image: | ||||
| 
 | ||||
|         $ docker pull debian:buster-slim | ||||
|         # We pull the "slim" image to save disk space | ||||
| 
 | ||||
| 1. Here is an application that echos "Hello, world." and then exits (this | ||||
| file is also in the current directory). | ||||
| 
 | ||||
|         #!/bin/sh | ||||
|         echo "Hello, world." | ||||
|         exit 0 | ||||
| 
 | ||||
| 1. Now we create a "Dockerfile" that tells the build process how to create | ||||
| the image. We use `debian:buster-slim` as the base and "add" the command | ||||
| `run.sh`. The first argument of `ADD` is the *local* copy of the file and | ||||
| the second argument is where we want the file to be in the image. | ||||
| 
 | ||||
|         # Dockerfile | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD run.sh /root/run.sh | ||||
| 
 | ||||
| 
 | ||||
| 1. We want to make sure that the script will run, so make it executable. | ||||
| 
 | ||||
|         # Dockerfile | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD run.sh /root/run.sh | ||||
|         RUN chmod a+x /root/run.sh | ||||
| 
 | ||||
| 1. Docker containers must be told which command to run. We do this with | ||||
| the `CMD` directive | ||||
| 
 | ||||
|         # Dockerfile | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD run.sh /root/run.sh | ||||
|         RUN chmod a+x /root/run.sh | ||||
|         CMD /root/run.sh | ||||
| 
 | ||||
| 1. We are now ready to build the image: | ||||
| 
 | ||||
|         $ docker build . -t hello-world | ||||
|         $ docker images | grep hello-world | ||||
| 
 | ||||
| 1. Question: what is the purpose of `.` (dot) in the above `docker build` | ||||
| command? | ||||
| 
 | ||||
| 1. Note that the tag is `latest` (the default). | ||||
| 
 | ||||
| 1. Let's run this image in a container: | ||||
| 
 | ||||
|         $ docker run --rm hello-world | ||||
| 
 | ||||
| 1. Did you see what you expected? | ||||
| 
 | ||||
| @ -1,3 +0,0 @@ | ||||
| #!/bin/sh | ||||
| echo "Hello, world." | ||||
| exit 0 | ||||
| @ -1,7 +0,0 @@ | ||||
| # Dockerfile | ||||
| FROM debian:buster-slim | ||||
| LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
| ADD run.sh /root/run.sh | ||||
| RUN chmod a+x /root/run.sh | ||||
| CMD /root/run.sh | ||||
| @ -1,7 +0,0 @@ | ||||
| # Dockerfile | ||||
| FROM debian:buster-slim | ||||
| LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
| ADD run.sh /root/run.sh | ||||
| RUN chmod a+rx /root/run.sh | ||||
| CMD /root/run.sh | ||||
| @ -1,211 +0,0 @@ | ||||
| # Lesson 3: Image layers | ||||
| 
 | ||||
| 1. Change directory into `lesson03`. | ||||
| 
 | ||||
| 1. Build the `hello-world` image with the tag `v1`: | ||||
| 
 | ||||
|         $ docker build . -t hello-world:v1 | ||||
| 
 | ||||
| 1. Each Docker image consists of a sequence of file system _layers_ with the | ||||
| later layers overwriting earlier ones. Each layer corresponds to an | ||||
| instruction in the `Dockerfile`. Let's look at the layers. (Note: in this and | ||||
| subsequent displays I leave off the CREATED column to save space). | ||||
| 
 | ||||
|         $ docker history hello-world:v1 | ||||
|         IMAGE          CREATED BY | ||||
|         f38d648975d5   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         09b895731b10   /bin/sh -c chmod a+x /root/run.sh | ||||
|         07d951eff6a0   /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... | ||||
|         34fd6d6fbb15   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
|         $ docker history debian:buster-slim | ||||
|         IMAGE          CREATED BY | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
| 1. Notice that the `hello-world:v1` image's first two layers are the same | ||||
| as `debian:buster-slim`'s layers. This reflects the fact that | ||||
| `hello-world:v1` starts FROM the `debian:buster-slim` image. | ||||
| 
 | ||||
| 1. The subsequent layers of the `hello-world:v1` image each correspond to | ||||
| the commands in the `Dockerfile`. Here is the Dockerfile again as a | ||||
| reminder: | ||||
| 
 | ||||
|         # Dockerfile | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD run.sh /root/run.sh | ||||
|         RUN chmod a+x /root/run.sh | ||||
|         CMD /root/run.sh | ||||
| 
 | ||||
| 1. When you create a running Docker container using `docker run` the | ||||
| Docker system loads your image with each layer being _read-only_ with a | ||||
| final, new layer being added. This last layer is writable. Let's try it. | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle busybox /bin/sh -c "sleep 10000" & | ||||
|         $ docker exec -ti fuzzle /bin/sh (we "login" to the running container) | ||||
|         / # cd /root; ls | ||||
|         ~ # date > date.txt; ls -l | ||||
|         -rw-r--r--    1 root     root             0 Jan 16 03:56 date.txt | ||||
|         ~ # cat /root/date.txt | ||||
|         Sat Jan 16 03:57:15 UTC 2021 | ||||
|         ~ # exit | ||||
| 
 | ||||
| 1. The container is still running. Let's make sure the file we created is | ||||
| still there. | ||||
| 
 | ||||
|         $ docker exec -ti fuzzle /bin/sh | ||||
|         ~ # cat /root/date.txt | ||||
|         Sat Jan 16 03:57:15 UTC 2021 | ||||
|         ~ # exit | ||||
| 
 | ||||
| 1. However, once the container exits, that final writable layer is thrown away. | ||||
| **It does not persist**. Let's see. | ||||
| 
 | ||||
|         $ docker rm fuzzle | ||||
|         # You should get an error here. Why? | ||||
| 
 | ||||
| 1. To stop a running container from the outside use the `docker kill` command. | ||||
| 
 | ||||
|         $ docker kill fuzzle | ||||
|         $ docker rm fuzzle | ||||
|         # This last command returns an error. Why? | ||||
| 
 | ||||
| 1. To see that the file `/root/date.txt` is really gone, let's start the | ||||
| container again and look. | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle busybox /bin/sh -c "sleep 10000" & | ||||
|         $ docker exec fuzzle ls -l /root | ||||
| 
 | ||||
| 1. Notice that we did not login to the container to do an `ls`, rather, we used | ||||
| the `exec` command. | ||||
| 
 | ||||
| 1. To reiterate, changes made to the containers top writable layer do not | ||||
| persist. If you want the container to make persistent changes to files | ||||
| another mechanism is needed such as mounting an external volume or writing | ||||
| to some other persistent data store; more on that in a later lesson. | ||||
| 
 | ||||
| 1. Back to layers. | ||||
| 
 | ||||
| 1. When we looked at the history of the `hello-world:v1` image we saw this: | ||||
| 
 | ||||
|         $ docker history hello-world:v1 | ||||
|         IMAGE          CREATED BY | ||||
|         f38d648975d5   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         09b895731b10   /bin/sh -c chmod a+x /root/run.sh | ||||
|         07d951eff6a0   /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... | ||||
|         34fd6d6fbb15   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
| 1. Under the `IMAGE` column are hex strings. Those correspond to the | ||||
| SHA256 hash of the new layer's content. (More precisely, the SHA256 hash | ||||
| of the layers configuration object.) | ||||
| 
 | ||||
| 1. Think of these hashes (roughly) corresponding to git commit hashes. The | ||||
| layers and their hashes are useful for they tell Docker when a layer has | ||||
| changed. We use a copy of `Dockerfile` with one small change: | ||||
| 
 | ||||
|         # Dockerfile-v2 | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD run.sh /root/run.sh | ||||
|         RUN chmod a+rx /root/run.sh  # This is the changed line. | ||||
|         CMD /root/run.sh | ||||
| 
 | ||||
| 1. Build the image and look at its history: | ||||
| 
 | ||||
|         $ docker build . -f Dockerfile-v2 -t hello-world:v2 | ||||
|         $ docker history hello-world:v2 | ||||
|         IMAGE          CREATED BY | ||||
|         0dd6cfe9bb51   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         39085b76a64f   /bin/sh -c chmod a+rx /root/run.sh | ||||
|         07d951eff6a0   /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786... | ||||
|         34fd6d6fbb15   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
|         $ docker history hello-world:v1 | ||||
|         IMAGE          CREATED BY | ||||
|         f38d648975d5   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         09b895731b10   /bin/sh -c chmod a+x /root/run.sh | ||||
|         07d951eff6a0   /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... | ||||
|         34fd6d6fbb15   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
| 1. Note that the layers have the same image ID except for the layers | ||||
| starting with the one we changed. The top layers have different id's | ||||
| since even though they are the same Dockerfile command they derive from | ||||
| the layer that changed (again, think of a git commit). | ||||
| 
 | ||||
| 1. This has the pleasant result that if the first N Dockerfile commands do | ||||
| not change but the N+1'st command _does_ change, then `docker build` will | ||||
| use the cached layers for the first N layers and only rebuild from layer | ||||
| N+1 onwards. This means rebuilds can be quite fast. | ||||
| 
 | ||||
| 1. **Important.** The image changes whenever one or more of the Dockerfile | ||||
| `ADD`, `COPY`, or `RUN` command lines change. Even adding whitespace to a | ||||
| line that has no real effect can trigger a new layer. | ||||
| 
 | ||||
| 1. What does it mean for a Dockerfile line to change? Either the line in | ||||
| the Dockerfile itself changes (as we saw above) **or**, for commands that | ||||
| add files from the local environment, the file being added changes. For | ||||
| example, if we added a comment line to `run.sh` this counts as a change to | ||||
| the `ADD run.sh /root/run.sh` line and would trigger a layer rebuild from | ||||
| that layer forward. | ||||
| 
 | ||||
| 1. Edit the file `run.sh` by adding a comment and then rebuild: | ||||
| 
 | ||||
|         #!/bin/sh | ||||
|         # An extra comment | ||||
|         echo "Hello, world." | ||||
|         exit 0 | ||||
| 
 | ||||
|         $ docker build . -t hello-world:v3 | ||||
|         $ docker history hello-world:v1 | ||||
|         IMAGE          CREATED BY | ||||
|         2131b85a91d5   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         30a489d1e5b0   /bin/sh -c chmod a+x /root/run.sh | ||||
|         94667dcd1388   /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786... | ||||
|         ebca792f7949   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
|         $ docker history hello-world:v3 | ||||
|         IMAGE          CREATED BY | ||||
|         acc54a5d1b49   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/roo... | ||||
|         82b9cde2c517   /bin/sh -c chmod a+x /root/run.sh | ||||
|         2a19083a38b0   /bin/sh -c #(nop) ADD file:997845d6d2fdc9492... | ||||
|         ebca792f7949   /bin/sh -c #(nop)  LABEL maintainer=adamhl@s... | ||||
|         589ac6f94be4   /bin/sh -c #(nop)  CMD ["bash"] | ||||
|         <missing>      /bin/sh -c #(nop) ADD file:422aca8901ae3d869... | ||||
| 
 | ||||
| 1. Notice that the `ADD` line has changed its image id. | ||||
| 
 | ||||
| 1. A gotcha: your Dockerfile installs a package downloaded from an | ||||
| external repository (like Debian). You build the image. A little while | ||||
| later the package is updated in the Debian repository. You rebuild the | ||||
| container thinking the rebuild will catch this change. But is does | ||||
| NOT. The docker build only notices changes to the Dockerfile itself, not | ||||
| to sources external to the build environment. | ||||
| 
 | ||||
| 1. A way around this gotcha. If you want to be sure that every time you | ||||
| build your container it rebuilds each layer regardless of any changes to | ||||
| the Dockerfile use the `--no-cache` option. This has the advantage of | ||||
| reliability but the disadvantage that it can take significantly more time. | ||||
| 
 | ||||
|         $ docker build . -t hello-world:v3            (this will be fast) | ||||
|         $ docker build . --no-cache -t hello-world:v3 (this will slower) | ||||
| 
 | ||||
| 1. Another advantage of Docker caching layers is that different images | ||||
| built off the same layer help save space. Recall that images layers are | ||||
| loaded read-only, so if two different running containers are built off the | ||||
| same layer, both containers can point to the same physical file. This is | ||||
| why spawning a second container running on the same image as the first can | ||||
| be so quick: the base layer is already loaded into memory so all that has | ||||
| to be done is add the final writable layer that is distinct for each | ||||
| container. | ||||
| @ -1,4 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # Hello | ||||
| echo "Hello, world." | ||||
| exit 0 | ||||
| @ -1,7 +0,0 @@ | ||||
| # Dockerfile (version 1) | ||||
| FROM debian:buster-slim | ||||
| LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
| ADD date.sh /root/date.sh | ||||
| RUN chmod a+x /root/date.sh | ||||
| CMD /root/date.sh | ||||
| @ -1,100 +0,0 @@ | ||||
| # Lesson 4: Persisting data | ||||
| 
 | ||||
| 1. Change directory into `lesson04`. | ||||
| 
 | ||||
| 1. We saw in Lesson 3 that data written to the final container layer does | ||||
| not persist. How do we deal with this? One option is to have the container | ||||
| write to an external system like a database system or cloud storage. There | ||||
| is, however, another method: mounting a local file system or directory. | ||||
| 
 | ||||
| 1. Let's create a Docker container that saves the current date to a file. Here is | ||||
| our first attempt: | ||||
| 
 | ||||
|         # Dockerfile | ||||
|         FROM debian:buster-slim | ||||
|         LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
|         ADD date.sh /root/date.sh | ||||
|         RUN chmod a+x /root/date.sh | ||||
|         CMD /root/date.sh | ||||
| 
 | ||||
|         # date.sh | ||||
|         #!/bin/sh | ||||
|         echo "echoing date to /tmp/date.output" | ||||
|         date > /tmp/date.output | ||||
| 
 | ||||
|         $ docker build . -t date | ||||
|         $ docker run --rm --name=fuzzle date | ||||
|         echoing date to /tmp/date.output | ||||
| 
 | ||||
| 1. The above will create the file `/tmp/date.output` containing the date | ||||
| but we know that the file will not persist after the container stops | ||||
| running. To get around this we "mount" a local directory into the | ||||
| container's `/tmp` directory. By "local directory" we mean a directory on | ||||
| the computer where we run the our `docker` commands. We mount the colume | ||||
| when we run the container. | ||||
| 
 | ||||
|         $ mkdir -p /tmp/docker | ||||
|         $ docker run --rm --name=fuzzle --volume=/tmp/docker:/tmp date | ||||
|         echoing date to /tmp/date.output | ||||
|         $ cat /tmp/docker/date.output | ||||
| 
 | ||||
| 1. You can run a container and override the `CMD` command. | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle date ls -ld /etc | ||||
|         drwxr-xr-x 1 root root 4096 Jan 17 17:23 /etc/ | ||||
|         (The date.sh script did NOT run) | ||||
| 
 | ||||
| 1. This is especially useful when you want to login to the container and | ||||
| debug the filesystem or CMD command. | ||||
| 
 | ||||
| 1. You can overwrite a file in the image using the same `--volume` | ||||
| option. For example, let's overwrite `/etc/debian_version` with a | ||||
| different file. | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle date cat /etc/debian_version | ||||
|         10.7 | ||||
|         $ echo "fake-version" > /tmp/deb_ver | ||||
|         $ docker run --rm --name=fuzzle --volume=/tmp/deb_ver:/etc/debian_version date cat /etc/debian_version | ||||
|         fake-version | ||||
| 
 | ||||
| 1. If you mount an external directory onto a container directory | ||||
| **everything** in the directory in the container is **replaced** with the | ||||
| external directory. | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle date ls -l /usr | ||||
|         total 32 | ||||
|         drwxr-xr-x  2 root root 4096 Jan 11 00:00 bin | ||||
|         drwxr-xr-x  2 root root 4096 Nov 22 12:37 games | ||||
|         ... more ... | ||||
| 
 | ||||
|         # Create a directory in /tmp with a single file. | ||||
|         $ mkdir -p /tmp/usr; cp test.txt /tmp/usr | ||||
| 
 | ||||
|         # Mount /tmp/usr over /usr in the container | ||||
|         $ docker run --rm --name=fuzzle --volume=/tmp/usr:/usr date ls -l /usr | ||||
|         total 4 | ||||
|         -rw-r--r-- 1 52777 root 7 Jan 17 18:04 test.txt | ||||
| 
 | ||||
| 1. You can mount an external directory in read-only mode. This is | ||||
| particularly useful when injecting secrets or configuration information | ||||
| into a container. Look for the `ro` in the `--volume` option below. | ||||
| 
 | ||||
|         $ mkdir -p /tmp/secrets | ||||
|         $ echo "my-password" > /tmp/secrets/password | ||||
| 
 | ||||
|         $ docker run --rm --name=fuzzle --volume=/tmp/secrets:/secrets:ro date sleep 100000 & | ||||
| 
 | ||||
|         $ docker exec -ti fuzzle /bin/sh | ||||
|         # cat /secrets/password | ||||
|         my-password | ||||
|         # echo "another secret" >> /secrets/password | ||||
|         /bin/sh: 5: cannot create /secrets/password: Read-only file system | ||||
| 
 | ||||
|         $ docker kill fuzzle | ||||
| 
 | ||||
| 1. You can run the entire container in read-only mode. The `docker run` | ||||
| option `--read-only` mounts the root filesystem (i.e., everything) in | ||||
| read-only excepting any externally mounted volumes. This lets you | ||||
| lock-down the filesystem except for those parts of the filesystem you | ||||
| know need to be written to (e.g., `/tmp`, `/var/log`, etc.). | ||||
| @ -1,3 +0,0 @@ | ||||
| #!/bin/sh | ||||
| echo "echoing date to /tmp/date.output" | ||||
| date > /tmp/date.output | ||||
| @ -1 +0,0 @@ | ||||
| Hello. | ||||
| @ -1,36 +0,0 @@ | ||||
| # Lesson 5: Network access | ||||
| 
 | ||||
| 1. Change directory into `lesson05`. | ||||
| 
 | ||||
| 1. A very popular use for containers is for serving web applications. How | ||||
| do users get network access to a running container? Put simply, the host | ||||
| acts as the network proxy for its containers: a network conection is made | ||||
| to the host which directs the traffic into the container, and vice versa | ||||
| for outbound traffic. | ||||
| 
 | ||||
| 1. Let's try it: | ||||
| 
 | ||||
|         $ docker pull httpd | ||||
|         $ docker run -dit --name apache --rm -p 8080:80 httpd | ||||
|         $ docker ps | ||||
|         CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS          PORTS                  NAMES | ||||
|         23c146b78377   httpd     "httpd-foreground"   20 seconds ago   Up 19 seconds   0.0.0.0:8080->80/tcp   apache | ||||
| 
 | ||||
| 1. Note that traffic sent to port 8080 on the *host* gets sent to port 80 | ||||
| in the *container*. The `-d` option tells Docker to run the container in | ||||
| "detached" mode, i.e., in the background. | ||||
| 
 | ||||
| 1. Look at the network (look for the `Containers` element): | ||||
| 
 | ||||
|         $ docker network inspect bridge | ||||
| 
 | ||||
| 1. Test the application. If running docker on your local machine put this | ||||
| URL into your browser's address bar: `http://localhost:8080`. | ||||
| 
 | ||||
| 1. If running on a remote host run this command: | ||||
| 
 | ||||
|         $ wget http://localhost:8080 --quiet -O - | ||||
| 
 | ||||
| 1. Don't forget to kill the container: | ||||
| 
 | ||||
|         $ docker kill apache | ||||
| @ -1,7 +0,0 @@ | ||||
| # Dockerfile | ||||
| FROM debian:buster-slim | ||||
| LABEL maintainer="adamhl@stanford.edu" | ||||
| 
 | ||||
| ADD demo.sh /root/demo.sh | ||||
| RUN chmod a+x /root/demo.sh | ||||
| CMD /root/demo.sh | ||||
| @ -1,35 +0,0 @@ | ||||
| # Lesson 6: Environment variables and configuration | ||||
| 
 | ||||
| 1. Change directory into `lesson06`. | ||||
| 
 | ||||
| 1. A popular way to provide configuration information to a docker container is | ||||
| via environment variables. | ||||
| 
 | ||||
| 1. Let's try it: | ||||
| 
 | ||||
|         $ docker build . -t demo | ||||
|         $ docker run -it --rm --name fuzzle demo | ||||
| 
 | ||||
| 1. Does the output make sense? | ||||
| 
 | ||||
| 1. Let's pass in a value for the environment variable `HELLO_WORLD`: | ||||
| 
 | ||||
|         $ docker run -it --rm --name fuzzle -e HELLO_WORLD='Hello, world.' demo | ||||
| 
 | ||||
| 1. You can also pass in environment variables via a file: | ||||
| 
 | ||||
|         $ cat file.env | ||||
|         # We define two environment variables (you CAN comment!) | ||||
|         HELLO_WORLD="Hello, world." | ||||
|         ENV_VAR2="another environment variable" | ||||
| 
 | ||||
|         $ docker run -it --rm --name fuzzle --env-file=file.env demo | ||||
| 
 | ||||
| 1. If your Docker application does not have many configuration options | ||||
| configuring via environment variables is a good method. However, if the | ||||
| application has complicated or extnesive configuration this may not be | ||||
| feasible. | ||||
| 
 | ||||
| 1. Don't forget to clean up: | ||||
| 
 | ||||
|         $ docker rmi demo:latest (removes the image demo:latest) | ||||
| @ -1,9 +0,0 @@ | ||||
| #!/bin/bash | ||||
| # demo.sh | ||||
| 
 | ||||
| if [ -z "${HELLO_WORLD}" ]; then | ||||
|     echo "The environment variable HELLO_WORLD is not defined." | ||||
| else | ||||
|     echo "The environment variable HELLO_WORLD is '${HELLO_WORLD}'." | ||||
| fi | ||||
| 
 | ||||
| @ -1,3 +0,0 @@ | ||||
| # We define two environment variables (you CAN comment!) | ||||
| HELLO_WORLD="Hello, world." | ||||
| ENV_VAR2="another environment variable" | ||||
| @ -1,6 +0,0 @@ | ||||
| # Useful External Links | ||||
| 
 | ||||
| * [Container and | ||||
| layers](https://docs.docker.com/storage/storagedriver/#container-and-layers): | ||||
| a good explanation from Docker on image and container layers | ||||
| 
 | ||||
| @ -1,49 +0,0 @@ | ||||
| [[_TOC_]] | ||||
| 
 | ||||
| # Good Docker Practices | ||||
| 
 | ||||
| ## Keep the Docker image simple (micro-services) | ||||
| 
 | ||||
| Although you can run as many processes in a single container as you want, | ||||
| it is usually a good idea to design a container to do a single task. If | ||||
| your application does several different things you can always add | ||||
| "sidecar" containers that do the extra work. | ||||
| 
 | ||||
| There will be situations where splitting an application into different | ||||
| containers is too complicated.  Be flexible and use your own judgement. | ||||
| 
 | ||||
| ## Use small base images | ||||
| 
 | ||||
| A smaller image means faster start-up times and less memory used on the | ||||
| container host. One way to acheive is to use a small base image. A popular | ||||
| small image is `alpine` based on Alpine Linux. This is a complete Linux | ||||
| with image size of 5.5MB with its own packages. By comparison, | ||||
| `debian:buster-slim` is about 70MB. | ||||
| 
 | ||||
| On the other hand, don't let the drive toward small size get in the way | ||||
| of needed functionality; remember the IBM Pollyanna Principle: "machines | ||||
| should work; people should think". | ||||
| 
 | ||||
| ## When possible use container orchestration | ||||
| 
 | ||||
| Getting containers to interact and cooperate can be tricky, so use one of | ||||
| the orcestration tools like Kubernetes or Docker Compose to do this. | ||||
| 
 | ||||
| ## Use CI/CD (i.e., automation) to keep Docker images up-to-date | ||||
| 
 | ||||
| Set up automation to rebuild your Docker images periodically making sure | ||||
| that you disable caching when building. This way your image will have the | ||||
| most up-to-date and secure base images. | ||||
| 
 | ||||
| ## Send diagnostic output to standard output | ||||
| 
 | ||||
| In the traditional server world we are used to sending logs to files. With | ||||
| Docker containers it is usually better to send diagnostic output to | ||||
| standard output. Kubernetes and other orchestration tools are designed | ||||
| with the expectation that logging is sent to standard output. | ||||
| 
 | ||||
| ## Run containers in "read-only" mode | ||||
| 
 | ||||
| Running a Docker container in read-only mode helps to reduce the attack | ||||
| surface area of your application. Mount external volumes for those parts | ||||
| of the file system that need to be writable (`/var/log`, `/tmp`, etc.). | ||||
| @ -1,15 +0,0 @@ | ||||
| # Prequisites | ||||
| 
 | ||||
| 1. Installation of Docker. | ||||
| 
 | ||||
| 1. Test your Docker install (the hex ID shown below will differ): | ||||
| 
 | ||||
|         $ docker images | ||||
|         # REPOSITORY    TAG   IMAGE ID    CREATED         SIZE | ||||
|         ... more ... | ||||
| 
 | ||||
|         $ docker pull busybox | ||||
|         Using default tag: latest | ||||
|         latest: Pulling from library/busybox | ||||
|         e5d9363303dd: Pull complete | ||||
| 
 | ||||
| @ -1,23 +0,0 @@ | ||||
| # Summary of Docker commands used in this tutorial | ||||
| 
 | ||||
|     # List images | ||||
|     docker images | ||||
| 
 | ||||
|     # Pull an image from Docker Hub | ||||
|     docker pull busybox | ||||
| 
 | ||||
|     # Build a Docker image | ||||
|     docker build -t my-tag . | ||||
| 
 | ||||
|     # Build image ignoring cached layers | ||||
|     docker build --no-cache -t my-tag . | ||||
| 
 | ||||
|     # Run container in "interactive" mode | ||||
|     docker run -it <image-name> | ||||
| 
 | ||||
|     # List containers | ||||
|     docker ps | ||||
|     docker ps -a  (include stopped containers) | ||||
| 
 | ||||
|     # Connect to a running container (i.e., "login") | ||||
|     docker exec -it <container-name> -- /bin/bash | ||||
					Loading…
					
					
				
		Reference in new issue