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. Числа Фибоначчи |
|
||||||
|
|
||||||
Определим числа Фибоначчи следующим образом: |
|
||||||
|
|
||||||
![fibonacci](https://wikimedia.org/api/rest_v1/media/math/render/svg/caa1e3b2e9c19fe638fc0b3d6548718bfe5119e2) |
|
||||||
|
|
||||||
Вам нужно написать скрипт, который будет считывать из `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