- 投稿日:2020-05-19T22:13:41+09:00
alpineでC言語依存モジュールを pip install すると激重になる話
TL;DR
PyPiに上げられているC言語依存のpythonモジュールは、alpine標準のmuslには対応してないから毎回手元でコンパイルされるよ。
docker:alpineでどうしても使いたい場合は必要モジュールをビルドしたイメージを個別に用意しておくと良いよ。alpineさんちのpip事情
pythonのモジュール管理行うpip。そのpipで採用されており快適にモジュールの導入を可能にしているwheel形式だが、alpineに対応した
.whl
がPyPi上に存在しないモジュールがある。wheel
先に少しだけwheelの話。
wheelは元々Eggという形式の後継で作られたもので、Built Distribution
と呼ばれるインストール形式であるそう。
Built Distribution
はここの用語集を見る限りでは以下の通り。Distribution 形式のうち、中身のファイルとメタデータをターゲットシステムの正しい場所へ移動するだけでインストールができるもの。
Egg や Wheel の実体は zip/tar などの圧縮形式の拡張なので、
pip install
時はダウンロードして展開後にpipで管理している場所にファイルを移動しているだけとなる。なので速い。コンパイル済み拡張モジュールを含んだパッケージ1も同様で取得・展開・移動するだけなので、これによりストレスフリーに使うことができている訳だ。
...ただしPyPi上に使用しているOS・アーキテクチャに対応したwheelファイルが存在する場合のみである。2alineはmusl-libcで動く
alpineはmuslを採用している。
muslは軽量・高速・シンプルを目標に標準Cライブラリの実装を1から行っており、glibcなどの非標準な拡張ライブラリにも対応している。alpineにぴったりだ。
ただし、完全な互換はまだ実現できていない模様で稀に使いたい関数が存在しなかったりする。で、今回のここで書く内容もalpineがmuslを使用している事が原因で掲題通りの事象が発生している。
ちなみに読み方はマッスルらしい。つよそう。
alpineはglibcを使っていない
今日、多くのLinuxディストリビューションはglibcを採用しているし、バイナリも共有ライブラリとして動的リンクさせてるのも少なくない。
numpyなどC言語を使用しているモジュールも例に漏れず3、PyPiにアップロードされているwheelファイルはglibc環境下で動くことを想定したビルドがなされている。つまり musl lib上で動く事を想定していない。4
そのためalpine上でpip install numpy
をするとサポートされた.whl
が存在しないため.zip
やら.tar.gz
が降ってくる。ビルド前のソースコードを丸々落としてきているのだ。Collecting numpy Downloading numpy-1.18.3.zip (5.4 MB) |████████████████████████████████| 5.4 MB 1.3 MB/s Installing build dependencies ... doneダウンロード後はalpine用に一からコンパイルが実行されwheelファイルを作成する。そのあと出来上がったwheelファイルをpipは取り込み直すことでpython上でimportできるようにしている。
これがC言語依存モジュールをpip install
すると時間がかかる原因である。※ 他言語やOS・アーキテクチャに依存しないモジュールや、pureなpythonで書かれたモジュールだとalpineでもwheel形式で落ちてくるため時間はかからない。
浮かび上がる諸問題
C言語依存しているものでも数秒でコンパイルできるものもあるが、numpyを入れようとすると数分程度かかってしまうしnumpy依存のscipyなどを使おうとすると更に数十分コンパイルに時間がかかってしまうこともある。
一回限りのビルドで今後全ての開発環境を賄えるローカル環境であればそれでも良いかもしれないが、
製品やサービスをCI/CDを含めた環境構築することを考えていくとなると、膨大なコンパイル時間はとてつもないほど大きな障害となる。
git のブランチをフックにしてunitテストが走るCI環境、サーバーなどにサービスを安全にデリバリーするCD環境、etc、etc...
毎回毎回長いコンパイルが走ってしまうと細かい修正の確認ですら一時間単位で浪費してしまうことになるし、他の作業が滞ってしまう原因にもなる。
そしてなによりtwitterをする時間が減ってしまう。大問題だ。
実際に業務でpandasをalpineに突っ込んでしまった時は睡眠時間まで減ってしまったのだから、冗談抜きで死活問題である。回避策
これを防ぐためには以下のものが考えられる。
- alpineを諦める
- コンパイル済みdocker imageで対処する
- ベースのディストリビューションとして使う
- multi stage ビルドを利用する
alpineを諦める
最も手間が少なく考える時間をかける必要がない有効な方法である。
ubuntuやcentosなどコンパイル済みで手段も確立しているディストリビューションに乗り換えてしまうのだ。
ただし、既に作り込んでしまっていて容易に乗り換えられないケースもあるのでは無かろうか?
業務で詰まった時は次の方法を取った。コンパイル済みdocker imageで対処する
wheelのコンパイルが済んだ状態のdocker imageを自由に使える場所に置いてしまい、使いたい時にpullするというもの。
base-imageFROM python:3.8-alpine3.11 COPY require/requirements.txt /tmp RUN apk update && \ apk add --no-cache hoge-dev && \ pip install --upgrade --no-cache-dir pip setuptools wheel && \ pip install -r --no-cache-dir /tmp/requirements.txt先ずはこんな感じでimageを作っておき、
docker push
でdocker hubやawsのecrなどに保存しておく。docker push hoge/huga:latest2パターンあるがビルド完了しているものを使うという意味では同じ。
ベースのイメージとして使う
公式イメージなどと同じように
FROM
を使ってベースイメージとして使う。
簡単だが、コンパイル時のみに必要なライブラリを消し忘れたりするとこちらのイメージサイズも大きくなってしまうなど、base-image
dockerfileへの依存度が高くなりメンテナンス性が下がってしまう。builder-patternFROM hoge/huga:latestmulti stage ビルドとして使う
Docker17.05以上なら使える機能で、成果物だけイメージから取り出すことができる方法。
wheelコンパイルに関連するものはhoge/huga:latest
に封じ込められるので後々気が楽になるから個人的におすすめ。builder-patternFROM hoge/huga:latest as pip_build FROM python:3.8-alpine3.11 COPY --from=pip_build /usr/local/lib/ /usr/local/lib/ COPY --from=pip_build /usr/local/bin/ /usr/local/bin/ COPY --from=pip_build /usr/local/include/ /usr/local/include/上記だとディレクトリを直接張り付けてしまっているので、コンパイル済みの
*.whl
をhoge/huga:latest
から持ってきてpip install
する方が安全かも。欠点
解決策としなかったのは上記の方法だと、複数のdockerイメージのメンテナンスを避けられないためだ。
作成した実行環境よりもソースコードで使用するライブラリや書式の方が最新になってしまった場合が起こるたび、長大なコンパイルを実行しpushし直す必要が出てくる...
安眠できる日々は長くは続かなさそうだ。参考文献
- Using Alpine can make Python Docker builds 50× slower : 滅茶苦茶参考にしました。
- Python Packaging User Guide:用語集
- Introduction to musl
- お前のDockerイメージはまだ重い???
- マルチステージビルドの利用
おまけ記事
alpineでC言語依存モジュールを pip install した時の時間を計測してみた
用語集曰く
Binary Distribution
と呼ぶらしく、拡張モジュールごと移動させ使用している。 ↩PyPiのモジュールのページ左側[Navigation]->[Download files]から確認できる。 ↩
ダウンロードしたwheelを
unzip
で展開してやると*.so
が見える。pipが正しく動いている以上、OS・アーキテクチャに最適化されたものが存在しているはずだ。 ↩muslはアプリケーションを単一のポータブルなバイナリファイルとして配布できるように静的リンクに最適化している。(wikipediaから引用)
ld-musl-x86_64.so.1
しか存在しないのでld-linux-x86-64.so.2
にダイナミックリンクしているものは軒並み落ちる。 ↩
- 投稿日:2020-05-19T20:43:13+09:00
Dockerでの環境構築(Rails)超入門3 ~MySQLを立ち上げる~
これまで
Dockerでの環境構築(Rails)超入門1
Dockerでの環境構築(Rails)超入門2 ~Dockerfileの設定~やること
前々回立ち上げたRailsは初期設定で「sqlite3」に接続している
今回はMySQLサーバーを立ち上げ、複数コンテナを動作させる手順
- MySQLサーバーを立ち上げる
- Railsのプロジェクトを書き換える
実践
- MySQLサーバーを立ち上げる
1) docker-compose.yamlの編集
docker-compose.yamlversion: '3' services: mysql: image: mysql:8.0.20 command: --default-authentication-plugin=mysql_native_password volumes: - "./mysql-data:/var/lib/mysql" environment: MYSQL_ROOT_PASSWORD: root app: build: . volumes: - ".:/app" ports: - "3000:3000" tty: true depends_on: - mysql2)「mysql-data」フォルダをappと同じ階層に作成
3) Dockerfileに以下を追加
DockerfileFROM ruby:2.6.6-stretch RUN gem rails install RUN apt-get update && \ apt-get install -y node.js *mysql-client* COPY Gemfile/Gemfile COPY Gemfile.lock/Gemfile.lock RUN bundle install4) Mysqlに接続する
$ docker-compose up --build $ docker exec -it practice_app_1 /bin/bash /# mysql -u root -proot -h mysql > show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+2 . Railsのプロジェクトを書き換える
・Gemfileを以下に編集Gemfile(省略) gem 'sqlite3'→ gem *'mysql2' (省略)・config/database.yamlを編集
config/database.yamldefault: &default adapter: mysql2 enconding: utf8 username: root password: root host: mysql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default database: practice_development # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: practice_test production: <<: *default database: practice_production・app/#に移動して
rake db:create
でデータベースを作成・再度Mysqlに接続してデータベースが作成されていることを確認
+----------------------+ | Database | +----------------------+ | information_schema | | mysql | | performance_schema | | practice_development | | practice_test | | sys | +----------------------+最後に
rails s -b 0.0.0.0
でローカルに接続すれば開発環境の構築はOK!補足
- 別のコマンドでコンテナに入る
今まで
$ docker exec -it pracitce /bin/bash
でコンテナに入っていたが、「docker-compose.yaml」が入っているファイルがあれば、$ docker-compose exec コンテナ名(app)/bin/bash2 . コンテナに入ったあと、自動的にappに移動したい
1) docker-compose.yamlの編集docker-compose.yamlversion: '3' services: mysql: image: mysql:8.0.20 command: --default-authentication-plugin=mysql_native_password volumes: - "./mysql-data:/var/lib/mysql" environment: MYSQL_ROOT_PASSWORD: root app: build: . volumes: - ".:/app" ports: - "3000:3000" tty: true depends_on: - mysql #以下を追加 *working_dir: "/app"*これで
docker exec compose app /bin/bash
の後、自動的にapp/#に移動している
- 投稿日:2020-05-19T20:29:20+09:00
Docker / ECR / ECS コンテナ入門まとめ②
前後編
参考文献
- 【AWS】初めてのECR
- イメージのプッシュ
- AWS CLI の設定
- 既存のRailsアプリにDockerを導入する手順
- 既存のrailsプロジェクトをDockerで開発する手順
- Debianのdockerイメージでmysql-clientが無くてハマった人へ
- Dockerfile 記述のベストプラクティス
- database.yml の管理方法いろいろ
1. Dockerfile
◆ mysql-client問題
- Debian10 "buster"(Ubuntuの母らしい)では、"mysql-client"は存在しない
- "default-mysql-client"パッケージを使用する必要がある
DockerfileFROM ruby:2.6.5 ※GemfileのRubyバージョン要確認 RUN apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y default-mysql-client --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /app ※アプリケーション名 WORKDIR /app ADD Gemfile /app/Gemfile ADD Gemfile.lock /app/Gemfile.lock RUN bundle install ADD . /app2. docker-compose.yml
◆ ビルドコンテキストについて
- docker build実行時の"カレントディレクトリ"を指定する
- デフォルトでは、Dockerfileが"カレントディレクトリ"と認識される
docker-compose.ymlversion: '2' services: db: image: mysql:latest environment: MYSQL_DATABASE: データベース名 MYSQL_ROOT_PASSWORD: XXXXXXX MYSQL_USER: ユーザ名 MYSQL_PASSWORD: XXXXXXX ports: - "3306:3306" web: build: context: . dockerfile: Dockerfile command: bundle exec rails s -p 3000 -b '0.0.0.0' tty: true stdin_open: true depends_on: - db ports: - "3000:3000" volumes: - .:/app ※アプリケーション名3. database.yml
◆ 環境変数について
password: <%= ENV['DOCKER_DATABASE_PASSWORD'] %>$ export DOCKER_DATABASE_PASSWORD=password ※パスワードdatabase.yml
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: 5 host: db development: <<: *default username: ユーザ名 password: XXXXXXX database: データベース名 production: <<: *default database: データベース名 username: ユーザ名 password: <%= ENV['DOCKER_DATABASE_PASSWORD'] %>4. ECRプッシュ作業
AWS CLI設定 ※AWSマネジメントコンソール"マイセキュリティ資格情報"参照
$ aws configure --profile ecr AWS Access Key ID [None]: *********************** AWS Secret Access Key [None]: ************************* Default region name [None]: ap-northeast-1 Default output format [None]: jsonECRログインコマンド
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ***.ecr.ap-northeast-1.amazonaws.comイメージ作成
$ docker build -t 【イメージ名】 .タグ付け
$ docker tag イメージ名:タグ名 ***.dkr.ecr.ap-northeast-1.amazonaws.com/イメージ名:タグ名プッシュコマンド
$ docker push ***.dkr.ecr.ap-northeast-1.amazonaws.com/イメージ名:タグ名
- 投稿日:2020-05-19T18:21:41+09:00
Ubuntu 20.04 LTS 日本語Remixへの Docker と Docker-compose インストール
ubuntu 20.04 LTS 日本語Remix に DockerとDocker-compose をインストールしてみます。
導入の仕方を決める。
インストールされるパッケージを比較したら、aptでさくっと入れられるバージョンと
Docker公式から入手できる物に大きな差はなさそう。
と言う事で、簡単な標準リポジトリからインストールしちゃいます。2020年5月19日 の比較
パッケージ 標準リポジトリ Docker公式サイト docker.io 19.03.8-0 19.03.8 docker-compose 1.25.0-1 1.25.5 Docker/Docker-composeのインストール
依存関係の確認
Docker-composeをインストールすればDockerもインストールされるみたい。
user01@ubuntu20:~$ apt depends docker-compose docker-compose 依存: python3-cached-property (>= 1.2.0) 依存: python3-docker (>= 4.0.0) 依存: python3-dockerpty (>= 0.4.1) 依存: python3-docopt (>= 0.6.1) 依存: python3-jsonschema 依存: python3-requests (>= 2.20.0) 依存: python3-six (<< 2) 依存: python3-six (>= 1.3.0) 依存: python3-texttable (>= 0.9.0) 依存: python3-websocket (>= 0.32.0) 依存: python3-yaml (>= 3.10) 依存: <python3:any> (>= 3.6~) python3:i386 python3 依存: python3-distutils 推奨: docker.io (>= 1.9.0) user01@ubuntu20:~$Docker-composeのインストール
Docker-composeを指定してインストールをすれば、docker含め一式インストールできるから
インストールコマンドがとてもスッキリしてます。user01@ubuntu20:~$ sudo apt -y install docker-compose [sudo] user01 のパスワード: パッケージリストを読み込んでいます... 完了 依存関係ツリーを作成しています 状態情報を読み取っています... 完了 以下の追加パッケージがインストールされます: bridge-utils cgroupfs-mount containerd docker.io git git-man liberror-perl pigz python3-attr python3-cached-property python3-distutils python3-docker python3-dockerpty python3-docopt python3-importlib-metadata python3-jsonschema python3-more-itertools python3-pyrsistent python3-setuptools python3-texttable python3-websocket python3-zipp runc ubuntu-fan 提案パッケージ: ifupdown aufs-tools btrfs-progs debootstrap docker-doc rinse zfs-fuse | zfsutils git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui gitk gitweb git-cvs git-mediawiki git-svn python-attr-doc python-jsonschema-doc python-setuptools-doc 以下のパッケージが新たにインストールされます: bridge-utils cgroupfs-mount containerd docker-compose docker.io git git-man liberror-perl pigz python3-attr python3-cached-property python3-distutils python3-docker python3-dockerpty python3-docopt python3-importlib-metadata python3-jsonschema python3-more-itertools python3-pyrsistent python3-setuptools python3-texttable python3-websocket python3-zipp runc ubuntu-fan アップグレード: 0 個、新規インストール: 25 個、削除: 0 個、保留: 0 個。 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 user01@ubuntu20:~$Dockerサービスの有効化
インストール後、docker.serviceを有効化しましょう。
user01@ubuntu20:~$ systemctl list-unit-files |grep docker docker.service disabled enabled docker.socket enabled enabled user01@ubuntu20:~$ sudo systemctl start docker [sudo] user01 のパスワード: user01@ubuntu20:~$ sudo systemctl enable docker Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /lib/systemd/system/docker.service. user01@ubuntu20:~$ systemctl list-unit-files |grep docker docker.service enabled enabled docker.socket enabled enabled user01@ubuntu20:~$一般ユーザ(Docker管理者)をグループ docker に追加
一般ユーザ(Docker管理者)が sudo なしで docker を操作出来るように、グループdockerに追加します。
グループ docker に参加させることで sudo せずに dockerコマンドを実行できるようになります。
この設定をしなくてもsudoすれば良いだけなので必要に応じて任意で実行してください。
以下の設定例では $USER (ログイン中のユーザ) をDocker管理者に追加しています。
※ログイン中のユーザにはgroupの変更は反映されません。docker の操作前に再ログインしてください。user01@ubuntu20:~$ sudo usermod -aG docker $USER [sudo] user01 のパスワード: user01@ubuntu20:~$ id $USER uid=1000(user01) gid=1000(user01) groups=1000(user01),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),133(docker) user01@ubuntu20:~$動作確認
hello-world を 実行
こんなメッセージが表示されれば、ちゃんと動いてると思います。
user01@ubuntu20:~$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:6a65f928fb91fcfbc963f7aa6d57c8eeb426ad9a20c7ee045538ef34847f44f1 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ user01@ubuntu20:~$参考
Ubuntuを入手:Ubuntu Japanese Team
Dockerインストール手順(テスト手順を参照):Install Docker Engine on Ubuntu
- 投稿日:2020-05-19T17:46:49+09:00
Dockerの開発環境で通常の開発言語のコマンドで実行する方法
Dockerの開発環境ではDockerの開発環境特有のコマンドでの操作が必要になりますが、スクリプト言語のコマンドをローカル環境で実行するときと同じコマンドで開発ができる方法を紹介します。
環境
Docker 19.03.8
Ruby 2.5
Rails 5.2Dockerとローカルの開発環境で実行するときの違い
例えば以下のRuby on Railsの開発で用いられるコマンド
$ rails db:migrateこのコマンドをDockerで実行する場合
$ docker-compose run web rails db:migrateこのようなコマンドになります。
それ以外にも操作方法がありますが、Ruby on Railsで開発するならRubyやRailsの通常のコマンドの方が楽ですよね。その場合は以下の手順でコマンドを実行します。Rubyの通常のコマンドで開発を行うための準備
Dockerを立ち上げます。
$ docker-compose startコンテナ名を調べます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a9b19e00552e recipegram_web "entrypoint.sh bash …" 44 hours ago Up 11 seconds 0.0.0.0:3000->3000/tcp recipegram_web_1 975875c12c76 postgres "docker-entrypoint.s…" 46 hours ago Up 12 seconds 5432/tcp recipegram_db_1コンテナ名を入れて下記のコマンドを実行します。
$ docker exec -it a9b19e00552e bashそうするとこのように切り替わるのでこの状態で今回であればRubyやRailsの実行コマンドで開発が行えます^_^
root@a9b19e00552e:/myapp#
- 投稿日:2020-05-19T14:32:19+09:00
DockerでJupyter、JupyterLabを使うまで
DockerでJupyter、JupyterLabを使うまで
実行環境
- Windows10 (64bit)
原理的にはこの方法で行えばMacでもできると思います。
設定手段
docker-anacondaを参考にdocker pullを行う。
docker pull jupyter/datascience-notebookその後書かれた通りに以下を実行すると、ハマる。
docker run -i -t -p 8888:8888 continuumio/anaconda3 /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser"Running as root is not recommended. Use --allow-root to bypass.そこで、
--allow-root
を加えろとの言われたので加える。docker run -i -t -p 8888:8888 continuumio/anaconda3 /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser --allow-root"上記のように実行するとlocalhostのurlが出てくるので実行するとJupyter notebookが使える。
docker run -i -t -p 8888:8888 continuumio/anaconda3 /bin/bash -c "/opt/conda/bin/conda install jupyter -y --quiet && mkdir /opt/notebooks && /opt/conda/bin/jupyter-lab --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser --allow-root"上記のように実行するとJupyterLabを使うことができる。
参考文献
https://hub.docker.com/r/continuumio/anaconda3/
https://github.com/ContinuumIO/docker-images/issues/94
- 投稿日:2020-05-19T14:20:23+09:00
今更だけどDockerについて学んでみた
Dockerとは?
Dockerとは、従来のホスト型(VMware等)やハイパーバイザー型(ESXi等)の仮想マシン技術とは異なり、ホストOS上に用意した仮想空間”コンテナ”内で、OSやアプリケーションを実行できる仮想コンテナ技術を提供するOSSソフトウェアのこと。
2013年にdotCloud社(現 Docker Inc.)からリリースされた。現在は「DevOps環境の構築」や「ソフトウェア開発環境の構築」等の様々な分野で注目されている。
無償版と有償版
現在、Dockerエンジンには大きくわけて2種類存在する。1つは、無償版のDockerエンジンであり、もう1つは有償版のDockerエンジンである。無償版のDockerエンジンは、Docker Community Edition(DockerCE)と呼ばれる。一方有償版のDockerエンジンは、Docker Enterprise Edition(DockerEE)と呼ばれる。
活用事例
メリット/デメリット
メリット
- 「アプリケーションの開発環境と実行環境をパッケージ化」し、迅速な配備や破棄が可能である。
- 仮想化技術の一種であり、1台の物理サーバ上に複数のコンテナを集約できるため、管理のコストや不可が削減できる
- 従来の仮想マシン(VMware等)と比較して、仮想OSを複数立ち上げる必要がなく、リソースの消費を抑えれるため、非常に軽量である
- イミューダブル・インフラストラクチャ(一旦、サーバを構築すると、ソフトウェアのバージョンアップやパッチ適用等の煩雑な管理を一切放棄するシステム)が可能である
- 『Docker Hub』と呼ばれるコミュニティサービスが活発であるため、既に多くのアプリケーションイメージが提供されている
デメリット
- 大量のDockerコンテナの管理が大変である。そこで、コンテナ化したアプリケーションのデプロイ、スケーリング、及び管理を行うためにコンテナオーケストレーション(コンテナを効率良く運用・開発するための技術)システムであるKubernetesが利用されるケースが増加している。
- キャパシティプランニング(計画・開発中あるいは稼働中のITシステムに求められるサービス需要/サービスレベルからシステムリソースの処理能力や数量などを見積もり、最適なシステム構成を計画すること)のベストプラクティスの事例が少ないため、明確に定義することが難しい。
- ライブマイグレーション(動作中の仮想マシンを停止させることなく、別のサーバに移動して処理を継続させる機能)をサポートしていない。
- ホストOS上のコンテナで稼働させるOSに制約がある。例えば、ホストOSがLinuxの場合には、その上で稼働するコンテナもLinuxに限定されてしまう。
概要と概要図
- Dokcerエンジン: アプリケーションのパッケージ化やコンテナの実行を担う
- Dockerイメージ: OSやアプリケーションを含んだテンプレートのベースイメージ
- Docker Hub レジストリ :公開されているDockerイメージをSaaS経由で提供する
- Dokcer プライベートレジストリ :ローカルで作成・保管したイメージの保管庫
- Docker コンテナ:分離された名前空間とアプリケーションの実行環境
- Docker クライアント :ユーザがコマンドを発行し、Dockerデーモンと通信を行う
Dockerでは、様々なOSとアプリケーションをパッケージ化された環境が、Docker Hubと呼ばれるレジストリサービスで用意されている。Docker Hubは「Docker Hub レジストリ」とも呼ばれ、インターネットを経由して、OS環境とアプリケーションを含んだイメージを入手することができる。
このDocker Hubから入手したイメージは「Dockerイメージ」と呼ばれる。Dockerイメージは、ベースイメージとも呼ばれており、Linux OSとアプリケーションを含んだ一種のテンプレートである。
Dockerのレジストリには、世界中のユーザや開発者が作成したDockerイメージが大量に保管されている。イメージを一から作成することも可能であるが、既にDocker Hubに用意されているイメージをそのまま利用できるため、OSやアプリケーションのインストールや初期設定等にかかる煩雑な作業工程を大幅に削減できるメリットがある。
一方で、インターネットを使わずに、ローカルのシステム用に配信するイメージの保管庫は「Docker プライベートレジストリ」と呼ばれる。ローカルのシステムにおいて、OSとアプリケーションのパッケージ化や、イメージを使ったコンテナの実行は、Docker本体が担う。このDockerの本体は、「Dockerエンジン」と呼ばれる。Dockerエンジンが実行するコンテナは、「Dockerコンテナ」と呼ばれる。
Dockerコンテナは、ホストOS上で複数同時に起動させることができ、ホストOSから見ると分離された名前空間として見える。通常ユーザは、Dockerデーモンが稼働するホストOS上でコマンドラインから操作を行うが、このDockerデーモンの操作を担う各種コマンドなどのインターフェースは「Dockerクライアント」と呼ばれる。Dockerクライアントは、Dockerデーモンが稼働するホストOS上でコマンドラインで操作できるが、遠隔地にあるDockerデーモンと通信を行うことも可能である。
- 投稿日:2020-05-19T14:20:23+09:00
今更だけどDockerについて学んでみよう
Dockerとは?
Dockerとは、従来のホスト型(VMware等)やハイパーバイザー型(ESXi等)の仮想マシン技術とは異なり、ホストOS上に用意した仮想空間”コンテナ”内で、OSやアプリケーションを実行できる仮想コンテナ技術を提供するOSSソフトウェアのこと。
2013年にdotCloud社(現 Docker Inc.)からリリースされた。現在は「DevOps環境の構築」や「ソフトウェア開発環境の構築」等の様々な分野で注目されている。
無償版と有償版
現在、Dockerエンジンには大きくわけて2種類存在する。1つは、無償版のDockerエンジンであり、もう1つは有償版のDockerエンジンである。無償版のDockerエンジンは、Docker Community Edition(DockerCE)と呼ばれる。一方有償版のDockerエンジンは、Docker Enterprise Edition(DockerEE)と呼ばれる。
活用事例
メリット/デメリット
メリット
- 「アプリケーションの開発環境と実行環境をパッケージ化」し、迅速な配備や破棄が可能である。
- 仮想化技術の一種であり、1台の物理サーバ上に複数のコンテナを集約できるため、管理のコストや不可が削減できる
- 従来の仮想マシン(VMware等)と比較して、仮想OSを複数立ち上げる必要がなく、リソースの消費を抑えれるため、非常に軽量である
- イミューダブル・インフラストラクチャ(一旦、サーバを構築すると、ソフトウェアのバージョンアップやパッチ適用等の煩雑な管理を一切放棄するシステム)が可能である
- 『Docker Hub』と呼ばれるコミュニティサービスが活発であるため、既に多くのアプリケーションイメージが提供されている
デメリット
- 大量のDockerコンテナの管理が大変である。そこで、コンテナ化したアプリケーションのデプロイ、スケーリング、及び管理を行うためにコンテナオーケストレーション(コンテナを効率良く運用・開発するための技術)システムであるKubernetesが利用されるケースが増加している。
- キャパシティプランニング(計画・開発中あるいは稼働中のITシステムに求められるサービス需要/サービスレベルからシステムリソースの処理能力や数量などを見積もり、最適なシステム構成を計画すること)のベストプラクティスの事例が少ないため、明確に定義することが難しい。
- ライブマイグレーション(動作中の仮想マシンを停止させることなく、別のサーバに移動して処理を継続させる機能)をサポートしていない。
- ホストOS上のコンテナで稼働させるOSに制約がある。例えば、ホストOSがLinuxの場合には、その上で稼働するコンテナもLinuxに限定されてしまう。
概要と概要図
- Dokcerエンジン: アプリケーションのパッケージ化やコンテナの実行を担う
- Dockerイメージ: OSやアプリケーションを含んだテンプレートのベースイメージ
- Docker Hub レジストリ :公開されているDockerイメージをSaaS経由で提供する
- Dokcer プライベートレジストリ :ローカルで作成・保管したイメージの保管庫
- Docker コンテナ:分離された名前空間とアプリケーションの実行環境
- Docker クライアント :ユーザがコマンドを発行し、Dockerデーモンと通信を行う
Dockerでは、様々なOSとアプリケーションをパッケージ化された環境が、Docker Hubと呼ばれるレジストリサービスで用意されている。Docker Hubは「Docker Hub レジストリ」とも呼ばれ、インターネットを経由して、OS環境とアプリケーションを含んだイメージを入手することができる。
このDocker Hubから入手したイメージは「Dockerイメージ」と呼ばれる。Dockerイメージは、ベースイメージとも呼ばれており、Linux OSとアプリケーションを含んだ一種のテンプレートである。
Dockerのレジストリには、世界中のユーザや開発者が作成したDockerイメージが大量に保管されている。イメージを一から作成することも可能であるが、既にDocker Hubに用意されているイメージをそのまま利用できるため、OSやアプリケーションのインストールや初期設定等にかかる煩雑な作業工程を大幅に削減できるメリットがある。
一方で、インターネットを使わずに、ローカルのシステム用に配信するイメージの保管庫は「Docker プライベートレジストリ」と呼ばれる。ローカルのシステムにおいて、OSとアプリケーションのパッケージ化や、イメージを使ったコンテナの実行は、Docker本体が担う。このDockerの本体は、「Dockerエンジン」と呼ばれる。Dockerエンジンが実行するコンテナは、「Dockerコンテナ」と呼ばれる。
Dockerコンテナは、ホストOS上で複数同時に起動させることができ、ホストOSから見ると分離された名前空間として見える。通常ユーザは、Dockerデーモンが稼働するホストOS上でコマンドラインから操作を行うが、このDockerデーモンの操作を担う各種コマンドなどのインターフェースは「Dockerクライアント」と呼ばれる。Dockerクライアントは、Dockerデーモンが稼働するホストOS上でコマンドラインで操作できるが、遠隔地にあるDockerデーモンと通信を行うことも可能である。
- 投稿日:2020-05-19T13:57:56+09:00
Docker, Docker compose コマンドメモ
- よく使う&忘れがちなdocker のコマンドを中心にメモ.
存在しているコンテナのうち,停止しているコンテナを起動させる
docker start (comtainer id)ex) docker stop f4a7cfeb3445起動しているコンテナを停止させる
docker stop (comtainer id)ex) docker stop f4a7cfeb3445今起動中のコンテナの一覧を表示
docker ps今存在している,起動中or停止中のコンテナの一覧を表示
docker ps -a
- 投稿日:2020-05-19T12:15:11+09:00
Docker コマンド
よく使うDockerコマンド集
実行中のコンテナ表示
$ docker ps存在するコンテナ表示
$ docker ps -aイメージの構築
$ docker-compose buildキャッシュがあるとき、イメージの構築、コンテナの構築・起動を一括でやってくれる
$ docker-compose up
-d
オプションを付けてバックグランドで実行$ docker-compose up -d
-build
キャッシュ無いときはbuildして、イメージを構築する$ docker-compose up --build -ddocker-compose.yml書いたら、これ実行している。
同じコンテナ名があるのでコンテナ作成できませんでしたという警告がでるときがある
$ docker stop python3 $ docker rm python3一旦動作しているコンテナを停止し、コンテナを削除する。
$ docker-compose exec python3 bash $ docker-compose exec db bash $ docker exec -it db bashDocker API
https://docs.docker.com/compose/reference/exec/Docker内の実行しているコンテナでbashを開始する
参考
docker-composeのupとbuildの違いについて
https://qiita.com/tegnike/items/bcdcee0320e11a928d46
- 投稿日:2020-05-19T12:15:11+09:00
Docker コマンド 作業メモ
よく使うDockerコマンド集
実行中のコンテナ表示
$ docker ps存在するコンテナ表示
$ docker ps -aイメージの構築
$ docker-compose buildキャッシュがあるとき、イメージの構築、コンテナの構築・起動を一括でやってくれる
$ docker-compose up
-d
オプションを付けてバックグランドで実行$ docker-compose up -d
-build
キャッシュ無いときはbuildして、イメージを構築する$ docker-compose up --build -ddocker-compose.yml書いたら、これ実行している。
同じコンテナ名があるのでコンテナ作成できませんでしたという警告がでるときがある
$ docker stop python3 $ docker rm python3一旦動作しているコンテナを停止し、コンテナを削除する。
$ docker-compose exec python3 bash $ docker-compose exec db bash $ docker exec -it db bashDocker API
https://docs.docker.com/compose/reference/exec/Docker内の実行しているコンテナでbashを開始する
参考
docker-composeのupとbuildの違いについて
https://qiita.com/tegnike/items/bcdcee0320e11a928d46
- 投稿日:2020-05-19T11:40:34+09:00
Dockerコンテナ実行時に、You must use Bundler 2 or greater with this lockfile.
dockerコンテナ上で
docker-compose exec app rails console
コマンドでrailsのコンソールを立ち上げようとしたところ、You must use Bundler 2 or greater with this lockfile.
と出てしまったときの対処法。[メモ]
docker-compose exec app gem install bundler docker-compose exec app bundle install以上。
- 投稿日:2020-05-19T09:12:41+09:00
Docker ミニ用語集 手順書フォルダ
Docker: Dockerはコンテナと呼ばれる仮想化のための技術。
Dockerfile: インフラの構成をコードに落とし込み宣言的に環境を構築する手法としての機能
Image: 特定の時点のスナップショットとしての機能を提供するもの学んだサイト/動画
https://qiita.com/phorizon20/items/57277fab1fd7aa994502
https://qiita.com/y-do/items/e127211b32296d65803a
https://www.youtube.com/watch?v=DS5HBTMG1RI&list=PLtpYHR4V8Mg-jbuk4yoXhXwJtreodnvzg
- 投稿日:2020-05-19T08:41:00+09:00
【備忘録】Rails3ルーティング確認
はじめに
今回はタイトルの通りにRailsにおけるルーティングの確認方法を残します。
触れているシステムのバージョンが古いため、Webブラウザでの確認が出来ずに詰まりました・・・。環境
Rails 3.0.19
docker-compose version 2Dockerコンテナからコマンドで確認する
1. サービス用コンテナを起動する
docker-compose up -d-d:デタッチド・モード: バックグラウンドでコンテナを実行し、新しいコンテナ名を表示
2. railsがインストールされているコンテナに入る(今回はappコンテナ)
docker-compose run app /bin/bashdockerやRailsにバージョンによって、コマンドが変わります。
直近で多く見られるコマンドは以下の通り。docker-compose exec app bash3. ルーティング確認コマンドを実行する
[root@[コンテナID] trunk]# bundle exec rake routes上記コマンドですと全件出力されてしまい見づらいため、grepコマンドを利用すると見やすいです。
[root@[コンテナID] trunk]# bundle exec rake routes | grep [絞り込みたい文字列]参考
- 投稿日:2020-05-19T05:12:29+09:00
macOS上でSQL Serverを使用してC#アプリを作成する
はじめに
この記事では、Microsoft 社が公開している Build an app using SQL Server の内容に従い、SQL Server を使用した C# アプリを作成します。
環境
- OS: macOS Catalina バージョン 10.15.4
- SQL Server: SQL Server 2019
- .NET Core: 3.1 LTS
環境のセットアップ
ここでは、SQL Server を Docker 上で取得します。その後、SQL Server で .NET Core アプリを作成するために必要な依存関係をインストールします。
SQL Server のインストール
- macOS で SQL Server を実行するには、SQL Server on Linux の Docker イメージ を使用します。そのためには、Docker for Mac をインストールする必要があります。
- Docker 環境に最低 4GB のメモリを設定し、パフォーマンスを評価したい場合は複数のコアを追加することも検討します。これは、メニューバーの [環境設定] -> [詳細設定] オプションで行うことができます。
- 新しいターミナルプロンプトを起動し、以下のコマンドを使用して SQL Server on Linux Docker イメージをダウンロードして起動します。
SA_PASSWORD の部分は特殊文字を使用した強力なパスワードを使用するように書き換えてください。
sudo docker pull microsoft/mssql-server-linux:2017-latest docker run -e 'HOMEBREW_NO_ENV_FILTERING=1' -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=yourStrong(!)Password' -p 1433:1433 -d microsoft/mssql-server-linux筆者は、Docker Desktop をインストールすると一緒に利用可能になる、Docker Compose を利用して、
SQL Server 2019 on Linux
をインストールしています。docker-compose.yaml については、以下の GitHub リポジトリを参照してください。なお、
ウェブ上で公開されている Docker イメージ
の URL は、Ubuntu ベースの SQL Server 2017 on Linux の Docker イメージ です。SQL Server 2019
では、従来の Ubuntu ベースに加え、RHEL ベース の SQL Server on Linux の Docker イメージ もサポートされるようになりました。
Ubuntu ベースを利用するか、RHEL ベースを利用するかは、好きな方を選択してください。(SQL Server における機能に差はありません)Homebrew と .NET Core のインストール
すでに .NET Core 3.1 LTS がインストールされている場合は、このステップをスキップしてください。
公式インストーラー をダウンロードして、.NET Coreをインストールします。インストーラーは、Build apps - SDK のものを選択してください。.NET Core 3.1.x SDK および、.NET Core ランタイムを一緒にインストールできます。
- ASP.NET Core Runtime
- Desktop Runtime
- .NET Core Runtime (上の2つを一緒にしたもの)
.NET Core では、作成したアプリを実行するためのランタイムが、種類によって分けられています。
ダウンロードサイト上では分かれて表示されているため、悩んでしまう可能性がありますが、ここでは気にしないでください。なお、
本家のサイトにあるリンクは .NET Core 2.0 のダウンロードリンク
になっています。これは既にサポート切れ、かつ LTS ではないため、最新の .NET Core 3.1.x をダウンロードし、インストールするようにしてください。.NET Core 3.1 は LTS バージョンになります。なお、筆者は、SQL Server 2019 と同様、Docker Compose を使用して、.NET Core 3.1 のコンテナーを作成し、開発を進めています。docker-compose.yaml については、以下の GitHub リポジトリを参照してください。
SQL Server および .NET Core 3.1 を Docker 上で利用する場合は、同じ docker-compose.yaml ファイル内に記述します。
container_name
を記述することで、コンテナ名を使ってコンテナ同士の相互通信が可能になります。docker-compose.yaml(例)version: '3' services: app: image: mcr.microsoft.com/dotnet/core/sdk:latest container_name: dotnetcoreapp tty: true ports: - 10080:80 volumes: - ./src:/src working_dir: "/src" mssql: image: mcr.microsoft.com/mssql/rhel/server:2019-latest container_name: 'mssql2019' environment: - MSSQL_SA_PASSWORD=databaseadmin@1 - ACCEPT_EULA=Y ports: - 1433:1433 # volumes: # Mounting a volume does not work on Docker for Mac # - ./mssql/log:/var/opt/mssql/log # - ./mssql/data:/var/opt/mssql/dataSQL Server を使った C# アプリケーションを作成
ここでは、以下、2 つのシンプルな C# アプリを作成します。
- 基本的な Insert、Update、Delete、Select を実行するアプリ
- .NET Core の ORM フレームワークの中でも特に人気のある Entity Framework Core を利用してInsert、Update、Delete、Select を実行するアプリ
SQL Server に接続してクエリを実行する C# アプリを作成
開発を行うワークディレクトリに移動し、新しい .NET Core プロジェクトを作成します。
基本的な .NET Core の Program.cs と csproj ファイルを含むプロジェクトディレクトリが作成されます。cd ~/ dotnet new console -o SqlServerSampleDocker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerSampleSqlServerSample.csproj というファイルが SqlServerSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerSample.csproj ファイルを開き、コードを以下の通りに書き換え、System.Data.SqlClient をプロジェクトに追加します。保存してファイルを閉じます。SqlServerSample.csproj<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> </ItemGroup> </Project>SqlServerSample ディレクトリ以下にある Program.cs ファイルを開き、コードを以下の通りに書き換え、保存してファイルを閉じます。
ユーザー名とパスワードを自分のものに置き換えることを忘れないでください。
Program.csusing System; using System.Data.SqlClient; namespace SqlServerSample { class Program { static void Main(string[] args) { try { // 接続文字列の構築 SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.DataSource = "localhost"; // 接続先の SQL Server インスタンス builder.UserID = "sa"; // 接続ユーザー名 builder.Password = "your_password"; // 接続パスワード builder.InitialCatalog = "master"; // 接続するデータベース(ここは変えないでください) // builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒 // SQL Server に接続 Console.Write("SQL Server に接続しています... "); using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) { connection.Open(); Console.WriteLine("接続成功。"); } } catch (SqlException e) { Console.WriteLine(e.ToString()); } Console.WriteLine("すべてが完了しました。任意のキーを押してアプリを終了します..."); Console.ReadKey(true); } } }SqlServerSample ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerSample dotnet restore
完了したら、ビルド実行を行います。
dotnet runなお、Docker Compose で実行している場合は、以下のようなコマンドを実行することで上記を実現できます。
docker-compose run -w /src/SqlServerSample --rm app dotnet restore docker-compose run -w /src/SqlServerSample --rm app dotnet runこれで、SQL Server に接続を行うコンソールアプリができました。ただし、このアプリでは単にデータベースへの接続だけを行っているだけで、クエリは実行していません。
次に、Program.cs 内にコードを追加して、データベースやテーブルの作成、INSERT/UPDATE/DELETE/SELECT などのクエリを実行するように変更します。
ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
書き換えた後、ファイルを保存し、プロジェクトをビルドして実行します。Program.csusing System; using System.Text; using System.Data.SqlClient; namespace SqlServerSample { class Program { static void Main(string[] args) { try { Console.WriteLine("SQL Server に接続し、Create、Read、Update、Delete 操作のデモを行います。"); // 接続文字列の構築 SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.DataSource = "localhost"; // 接続先の SQL Server インスタンス builder.UserID = "sa"; // 接続ユーザー名 builder.Password = "your_password"; // 接続パスワード builder.InitialCatalog = "master"; // 接続するデータベース(ここは変えないでください) // builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒 // SQL Server に接続 Console.Write("SQL Server に接続しています... "); using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) { connection.Open(); Console.WriteLine("接続成功。"); // サンプルデータベースの作成 Console.Write("既に作成されている SampleDB データベースを削除し、再作成します... "); String sql = "DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]"; using (SqlCommand command = new SqlCommand(sql, connection)) { // command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.ExecuteNonQuery(); Console.WriteLine("SampleDB データベースを作成しました。"); } // テーブルを作成しサンプルデータを登録 Console.Write("サンプルテーブルを作成しデータを登録します。任意のキーを押して続行します..."); Console.ReadKey(true); StringBuilder sb = new StringBuilder(); sb.Append("USE SampleDB; "); sb.Append("CREATE TABLE Employees ( "); sb.Append(" Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, "); sb.Append(" Name NVARCHAR(50), "); sb.Append(" Location NVARCHAR(50) "); sb.Append("); "); sb.Append("INSERT INTO Employees (Name, Location) VALUES "); sb.Append("(N'Jared', N'Australia'), "); sb.Append("(N'Nikita', N'India'), "); sb.Append("(N'Tom', N'Germany'); "); sql = sb.ToString(); using (SqlCommand command = new SqlCommand(sql, connection)) { // command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.ExecuteNonQuery(); Console.WriteLine("作成完了"); } // INSERT デモ Console.Write("テーブルに新しい行を挿入するには、任意のキーを押して続行します..."); Console.ReadKey(true); sb.Clear(); sb.Append("INSERT Employees (Name, Location) "); sb.Append("VALUES (@name, @location);"); sql = sb.ToString(); using (SqlCommand command = new SqlCommand(sql, connection)) { // command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.Parameters.AddWithValue("@name", "Jake"); command.Parameters.AddWithValue("@location", "United States"); int rowsAffected = command.ExecuteNonQuery(); Console.WriteLine(rowsAffected + " 行 挿入されました"); } // UPDATE デモ String userToUpdate = "Nikita"; Console.Write("ユーザー '" + userToUpdate + "' の 'Location' を更新するには、任意のキーを押して続行します..."); Console.ReadKey(true); sb.Clear(); sb.Append("UPDATE Employees SET Location = N'United States' WHERE Name = @name"); sql = sb.ToString(); using (SqlCommand command = new SqlCommand(sql, connection)) { // command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.Parameters.AddWithValue("@name", userToUpdate); int rowsAffected = command.ExecuteNonQuery(); Console.WriteLine(rowsAffected + " 行 更新されました"); } // DELETE デモ String userToDelete = "Jared"; Console.Write("ユーザー '" + userToDelete + "' を削除するには、任意のキーを押して続行します..."); Console.ReadKey(true); sb.Clear(); sb.Append("DELETE FROM Employees WHERE Name = @name;"); sql = sb.ToString(); using (SqlCommand command = new SqlCommand(sql, connection)) { // command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.Parameters.AddWithValue("@name", userToDelete); int rowsAffected = command.ExecuteNonQuery(); Console.WriteLine(rowsAffected + " 行 削除されました"); } // READ デモ Console.WriteLine("テーブルからデータを読み取るには、任意のキーを押して続行します..."); Console.ReadKey(true); sql = "SELECT Id, Name, Location FROM Employees;"; using (SqlCommand command = new SqlCommand(sql, connection)) { using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) { Console.WriteLine("{0} {1} {2}", reader.GetInt32(0), reader.GetString(1), reader.GetString(2)); } } } } } catch (SqlException e) { Console.WriteLine(e.ToString()); } Console.WriteLine("すべて完了しました。任意のキーを押して終了します..."); Console.ReadKey(true); } } }これで、macOS 上の .NET Core を使って、初めて C# + SQL Server アプリを作成できました。次は、ORM を使って C# アプリを作成します。
.NET Core で Entity Framework Core ORM を使用して SQL Server に接続する C# アプリを作成
ワークディレクトリに戻り、新しい.NET Coreプロジェクトを作成します。
cd ~/ dotnet new console -o SqlServerEFSampleDocker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerSampleEFSqlServerEFSample.csproj というファイルが SqlServerEFSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerEFSample.csproj ファイルを開き、コードを以下の通りに書き換え、Entity Framework Core をプロジェクトに追加します。保存してファイルを閉じます。SqlServerEFSample.csproj<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.4" /> </ItemGroup> </Project>このサンプルでは、2つのテーブルを作成します。1つ目は「ユーザー」に関するデータを保持し、もう1つは「タスク」に関するデータを保持するものです。
User.cs を作成します。
User クラスを定義します。SqlServerEFSample ディレクトリ以下に User.cs ファイルを作成します。
このクラスは、User テーブルに紐づくモデルのクラスです。書き換えた後、ファイルを保存して閉じます。
この時点では、Task クラスがないためコンパイルエラーとなりますが、問題ありません。
User.csusing System; using System.Collections.Generic; namespace SqlServerEFSample { public class User { public int UserId { get; set; } public String FirstName { get; set; } public String LastName { get; set; } public virtual IList<Task> Tasks { get; set; } public String GetFullName() { return this.FirstName + " " + this.LastName; } public override string ToString() { return "User [id=" + this.UserId + ", name=" + this.GetFullName() + "]"; } } }Task.cs を作成します。
Task クラスを定義します。SqlServerEFSample ディレクトリ以下に Task.cs ファイルを作成します。
このクラスは、Task テーブルに紐づくモデルのクラスです。書き換えた後、ファイルを保存して閉じます。Task.csusing System; namespace SqlServerEFSample { public class Task { public int TaskId { get; set; } public string Title { get; set; } public DateTime DueDate { get; set; } public bool IsComplete { get; set; } public virtual User AssignedTo { get; set; } public override string ToString() { return "Task [id=" + this.TaskId + ", title=" + this.Title + ", dueDate=" + this.DueDate.ToString() + ", IsComplete=" + this.IsComplete + "]"; } } }EFSampleContext.cs を作成します。
EFSampleContext クラスを定義します。SqlServerEFSample ディレクトリ以下に EFSampleContext.cs ファイルを作成します。
このクラスは、Entity Framework Core を使用し、.NET オブジェクトを利用してデータのクエリ、挿入、更新、および削除を行うためのクラスです。User クラスと Task クラスを使用しています。
書き換えた後、ファイルを保存して閉じます。EFSampleContext.csusing Microsoft.EntityFrameworkCore; namespace SqlServerEFSample { public class EFSampleContext : DbContext { string _connectionString; public EFSampleContext(string connectionString) { this._connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(this._connectionString); } public DbSet<User> Users { get; set; } public DbSet<Task> Tasks { get; set; } } }Entity Framework (.NET Framework) と違う点としては、OnConfiguring メソッドが新たにオーバーライドされ、逆に Database.SetInitializer(IDatabaseInitializer) を EFSampleContext のコンストラクタ内で指定しなくなっている点です。
最後に Program.cs を更新します。これまで作成したクラスを使用するための設定を行います。
ユーザー名とパスワードを自分のものに更新することを忘れないでください。
保存してファイルを閉じます。Program.csusing System; using System.Linq; using System.Data.SqlClient; using System.Collections.Generic; namespace SqlServerEFSample { class Program { static void Main(string[] args) { Console.WriteLine("** Entity Framework Core と SQL Server を使用した C# CRUD のサンプル **\n"); try { // 接続文字列を構築 SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.DataSource = "localhost"; // 接続先の SQL Server インスタンス builder.UserID = "sa"; // 接続ユーザー名 builder.Password = "your_password"; // 接続パスワード builder.InitialCatalog = "EFSampleDB";// 接続するデータベース(ここは変えないでください) // builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒 using (EFSampleContext context = new EFSampleContext(builder.ConnectionString)) { context.Database.EnsureDeleted(); context.Database.EnsureCreated(); Console.WriteLine("C#のクラスからデータベーススキーマを作成しました。"); // Create デモ: ユーザーインスタンスを作成し、データベースに保存 User newUser = new User { FirstName = "Anna", LastName = "Shrestinian" }; context.Users.Add(newUser); context.SaveChanges(); Console.WriteLine("\n作成されたユーザー: " + newUser.ToString()); // Create デモ: タスクインスタンスを作成し、データベースに保存 Task newTask = new Task() { Title = "Ship Helsinki", IsComplete = false, DueDate = DateTime.Parse("04-01-2017") }; context.Tasks.Add(newTask); context.SaveChanges(); Console.WriteLine("\nCreated Task: " + newTask.ToString()); // Association demo: Assign task to user newTask.AssignedTo = newUser; context.SaveChanges(); Console.WriteLine("\n作成されたタスク: '" + newTask.Title + "' 割り当てられたユーザー: '" + newUser.GetFullName() + "'"); // Read デモ: ユーザー 'Anna' に割り当てられた未完了のタスクを見つける Console.WriteLine("\n'Anna' に割り当てられた未完了のタスク:"); var query = from t in context.Tasks where t.IsComplete == false && t.AssignedTo.FirstName.Equals("Anna") select t; foreach(var t in query) { Console.WriteLine(t.ToString()); } // Update デモ: タスクの '期限' を変更 Task taskToUpdate = context.Tasks.First(); // 最初のタスクを取得 Console.WriteLine("\nタスクをアップデート中: " + taskToUpdate.ToString()); taskToUpdate.DueDate = DateTime.Parse("06-30-2016"); context.SaveChanges(); Console.WriteLine("変更された期限: : " + taskToUpdate.ToString()); // Delete デモ: 2016年が期限になっているすべてのタスクを削除 Console.WriteLine("\n期限が2016年になっているすべてのタスクを削除します。"); DateTime dueDate2016 = DateTime.Parse("12-31-2016"); query = from t in context.Tasks where t.DueDate < dueDate2016 select t; foreach(Task t in query) { Console.WriteLine("Deleting task: " + t.ToString()); context.Tasks.Remove(t); } context.SaveChanges(); // 'Delete' 操作の後にタスクを表示 - 0個のタスクがあるはず Console.WriteLine("\n削除後のタスク:"); List<Task> tasksAfterDelete = (from t in context.Tasks select t).ToList<Task>(); if (tasksAfterDelete.Count == 0) { Console.WriteLine("[なし]"); } else { foreach (Task t in query) { Console.WriteLine(t.ToString()); } } } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine("すべて完了しました。任意のキーを押して終了します..."); Console.ReadKey(true); } } }EFSampleContext クラスのコンストラクタで Database.SetInitializer(IDatabaseInitializer) を行っていないため、Program.cs 内で context.Database.EnsureDeleted() と context.Database.EnsureCreated() が行われていますね。
SqlServerSampleEF ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerEFSample dotnet restore
完了したら、ビルド実行を行います。
dotnet runDocker Compose で実行する場合は、以下のコマンドを実行してください。
docker-compose run -w /src/SqlServerEFSample --rm app dotnet restore docker-compose run -w /src/SqlServerEFSample --rm app dotnet runこれで、2つ目の C# アプリの作成が終わりました。最後に、SQL Server の カラムストア機能を使って C# アプリを高速化する方法について学びます。
C# アプリを 100 倍速にする
これまでで基本的なことは理解できたと思います。最後は、SQL Server を使用してアプリをより良くする方法を見てみます。このモジュールでは、カラムストアインデックスの簡単な例と、カラムストアインデックスがどのようにデータ処理速度を向上させるかを確認します。カラムストアインデックスは、従来の列ストアインデックスに比べて、分析ワークロードでは最大 100 倍のパフォーマンス向上、データ圧縮では最大 10 倍のパフォーマンス向上を実現できます。
カラムストアインデックスの機能を確認するために、500 万行のサンプルデータベースとサンプルテーブルを作成し、カラムストアインデックスを追加する前と後の簡単なクエリを実行する C# アプリケーションを作成します。
ワークディレクトリに戻り、新しい.NET Coreプロジェクトを作成します。
cd ~/ dotnet new console -o SqlServerColumnstoreSampleDocker Compose で行う場合は、以下の通りです。
docker-compose run --rm app dotnet new console -o SqlServerColumnstoreSampleSqlServerColumnstoreSample.csproj というファイルが SqlServerColumnstoreSample ディレクトリ以下に作成されます。
任意のテキストエディタで SqlServerColumnstoreSample.csproj ファイルを開き、コードを以下の通りに書き換え、System.Data.SqlClient をプロジェクトに追加します。保存してファイルを閉じます。SqlServerColumnstoreSample.csproj<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Data.SqlClient" Version="4.8.1" /> </ItemGroup> </Project>Program.cs の内容を書き換えます。
ユーザー名とパスワードは自分のものに置き換えることを忘れないでください。
保存してファイルを閉じます。Program.csusing System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SqlServerColumnstoreSample { class Program { static void Main(string[] args) { try { Console.WriteLine("*** SQL Server カラムストアのデモ ***"); // 接続文字列の構築 SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); builder.DataSource = "localhost"; // 接続先の SQL Server インスタンス builder.UserID = "sa"; // 接続ユーザー名 builder.Password = "your_password"; // 接続パスワード builder.InitialCatalog = "master"; // 接続するデータベース(ここは変えないでください) // builder.ConnectTimeout = 60000; // 接続タイムアウトの秒数(ms) デフォルトは 15 秒 // SQL Server に接続 Console.Write("SQL Serverへ接続しています... "); using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) { connection.Open(); Console.WriteLine("接続完了。"); // サンプルデータベースの作成 Console.Write("'SampleDB' を再作成しています... "); String sql = "DROP DATABASE IF EXISTS [SampleDB]; CREATE DATABASE [SampleDB]"; using (SqlCommand command = new SqlCommand(sql, connection)) { command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.ExecuteNonQuery(); Console.WriteLine("完了。"); } // 'Table_with_5M_rows' テーブルに500万行を挿入 Console.Write("テーブル 'Table_with_5M_rows' に500万行を挿入します。1分ほどかかりますが、お待ちください... "); StringBuilder sb = new StringBuilder(); sb.Append("USE SampleDB; "); sb.Append("WITH a AS (SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) AS a(a))"); sb.Append("SELECT TOP(5000000)"); sb.Append("ROW_NUMBER() OVER (ORDER BY a.a) AS OrderItemId "); sb.Append(",a.a + b.a + c.a + d.a + e.a + f.a + g.a + h.a AS OrderId "); sb.Append(",a.a * 10 AS Price "); sb.Append(",CONCAT(a.a, N' ', b.a, N' ', c.a, N' ', d.a, N' ', e.a, N' ', f.a, N' ', g.a, N' ', h.a) AS ProductName "); sb.Append("INTO Table_with_5M_rows "); sb.Append("FROM a, a AS b, a AS c, a AS d, a AS e, a AS f, a AS g, a AS h;"); sql = sb.ToString(); using (SqlCommand command = new SqlCommand(sql, connection)) { command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.ExecuteNonQuery(); Console.WriteLine("完了。"); } // カラムストアインデックスなしで SQL クエリを実行 double elapsedTimeWithoutIndex = SumPrice(connection); Console.WriteLine("カラムストアインデックスなしのクエリ時間: " + elapsedTimeWithoutIndex + "ms"); // カラムストアインデックスを追加 Console.Write("'Table_with_5M_rows' テーブルにカラムストアインデックスを追加中... "); sql = "CREATE CLUSTERED COLUMNSTORE INDEX columnstoreindex ON Table_with_5M_rows;"; using (SqlCommand command = new SqlCommand(sql, connection)) { command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 command.ExecuteNonQuery(); Console.WriteLine("完了。"); } // カラムストアインデックスが追加された後、再度同じ SQL クエリを実行 double elapsedTimeWithIndex = SumPrice(connection); Console.WriteLine("カラムストアありのクエリ時間: " + elapsedTimeWithIndex + "ms"); // カラムストアインデックスの追加によるパフォーマンス向上を計算 Console.WriteLine("カラムストアインデックスによる性能向上: " + Math.Round(elapsedTimeWithoutIndex / elapsedTimeWithIndex) + "x!"); } Console.WriteLine("すべて完了しました。任意のキーを押して終了します..."); Console.ReadKey(true); } catch (Exception e) { Console.WriteLine(e.ToString()); } } public static double SumPrice(SqlConnection connection) { String sql = "SELECT SUM(Price) FROM Table_with_5M_rows"; long startTicks = DateTime.Now.Ticks; using (SqlCommand command = new SqlCommand(sql, connection)) { try { command.CommandTimeout = 60000; // コマンドがタイムアウトする場合は秒数を変更(ms) デフォルトは 30秒 var sum = command.ExecuteScalar(); TimeSpan elapsed = TimeSpan.FromTicks(DateTime.Now.Ticks) - TimeSpan.FromTicks(startTicks); return elapsed.TotalMilliseconds; } catch (Exception e) { Console.WriteLine(e.ToString()); } } return 0; } } }SqlServerColumnstoreSample ディレクトリに戻り、以下のコマンドを実行して csproj 内の依存関係を復元します。
cd ~/SqlServerColumnstoreSample dotnet restore
完了したら、ビルド実行を行います。
dotnet runDocker Compose で実行する場合は、以下のコマンドを実行してください。
docker-compose run -w /src/SqlServerColumnstoreSample --rm app dotnet restore docker-compose run -w /src/SqlServerColumnstoreSample --rm app dotnet runおめでとうございます。カラムストアインデックスを使って C# アプリを高速化しました!
おわりに
以上で、「macOS上でSQL Serverを使用してC#アプリを作成する」は終了です。Build an app using SQL Server には、他言語での SQL Server アプリを作成するチュートリアルがあります。ぜひ、他の言語でも試してみてください。
- 投稿日:2020-05-19T02:03:34+09:00
DockerのUID/GIDあれこれ: Linux版通常モード & Docker Desktop for Mac編
1. はじめに
Dockerコンテナからボリュームマウントを通じてファイルを書き出す場合、ユーザID/グループID(以下、UID/GID)を指定しないとファイルのオーナが
root
(UID0
)になってしまい、困ることがあります。
docker container run
コマンドの--user
オプションでUID/GIDを指定することができますが、Linux版DockerとDocker Desktop for Macでは挙動に差があります。
これらの挙動について自分なりに整理するために、いろいろ実験しました。
なお、Dockerfile
のUSER
命令を使用してUID/GIDを指定する方法もありますが、今回はそちらについては触れていませんのでご了承ください。余裕があれば、Rootlessモード編、userns-remapモード編も書きたいと思います。というか、そちらが本命なのですが。
2. Linux版 通常モード
まずは、Linux版Dockerの通常モード(Rootlessモードでも、userns-remapモードでもない、一般的なモード)での挙動を調査しました。
2.1. 環境
調査した環境は以下の通りです。
- ハードウェア: Raspberry Pi 4 2GB版
- OS: Ubuntu Server 20.04 LTS ARM64(64ビット)版
- Docker Engine: 19.03.8
ubuntu$ cat /etc/os-release NAME="Ubuntu" VERSION="20.04 LTS (Focal Fossa)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.04 LTS" VERSION_ID="20.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=focal UBUNTU_CODENAME=focal ubuntu$ uname -a Linux ubuntu 5.4.0-1008-raspi #8-Ubuntu SMP Wed Apr 8 11:13:06 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux ubuntu$ sudo docker version Client: Version: 19.03.8 API version: 1.40 Go version: go1.13.8 Git commit: afacb8b7f0 Built: Wed Mar 11 23:43:15 2020 OS/Arch: linux/arm64 Experimental: false Server: Engine: Version: 19.03.8 API version: 1.40 (minimum version 1.12) Go version: go1.13.8 Git commit: afacb8b7f0 Built: Wed Mar 11 22:48:33 2020 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.3.3-0ubuntu2 GitCommit: runc: Version: spec: 1.0.1-dev GitCommit: docker-init: Version: 0.18.0 GitCommit: ubuntu$ sudo docker info ... Security Options: apparmor seccomp Profile: default ...なお、以下のコマンドは標準の
ubuntu
ユーザ(UID1000
、GID1000
)で実行しています。
ubuntu
ユーザはdocker
グループには意図的に入れていないため、docker
コマンドの実行にはsudo
を利用しています。
Rootlessモード、userns-remapモードは使用していませんが、念のため/etc/subuid
、/etc/subgid
の内容も示します。ubuntu$ id uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),115(netdev),118(lxd) ubuntu$ cat /etc/subuid ubuntu:100000:65536 ubuntu$ cat /etc/subgid ubuntu:100000:655362.2. 実験
実験は以下の通りです。インラインで説明を記載しています。
なお、ファイルの読み込み権限を分かりやすくするためにumask
でパーミッションのマスクを設定しています。
また、今回の実験ではUIDとGIDに同一の値を設定していますが、もちろん別々の値でも構いません。# 実験用のディレクトリを作成します。(他ユーザでも書き込める必要があります) ubuntu$ mkdir -p -m 777 /tmp/docker/normal # UID/GIDを指定せず、idコマンドを実行してUID/GIDを確認します。 # →UID/GIDは0:0です。 ubuntu$ sudo docker container run --tty --rm ubuntu:20.04 id uid=0(root) gid=0(root) groups=0(root) # UID/GIDを指定せず、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは0:0です。 ubuntu$ sudo docker container run --tty --rm --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/without_user && ls -ln /out/without_user" -rw------- 1 0 0 0 May 18 15:53 /out/without_user # UID/GIDとして1000:1000を指定し、idコマンドを実行してUID/GIDを確認します。 # →UID/GIDは1000:1000です。 ubuntu$ sudo docker container run --tty --user 1000:1000 ubuntu:20.04 id uid=1000 gid=1000 groups=1000 # UID/GIDとして1000:1000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは1000:1000です。 ubuntu$ sudo docker container run --tty --user 1000:1000 --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_1000 && ls -ln /out/with_user_1000" -rw------- 1 1000 1000 0 May 18 15:54 /out/with_user_1000 # UID/GIDとして2000:2000を指定し、idコマンドを実行してUID/GIDを確認します。 # →UIG/GIDは2000:2000です。 ubuntu$ sudo docker container run --tty --user 2000:2000 ubuntu:20.04 id uid=2000 gid=2000 groups=2000 # UID/GIDとして2000:2000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは2000:2000です。 ubuntu$ sudo docker container run --tty --user 2000:2000 --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_2000 && ls -ln /out/with_user_2000" -rw------- 1 2000 2000 0 May 18 15:54 /out/with_user_2000 # Dockerホスト側のファイルのUID/GIDを確認します。 # →UID/GIDはDockerコンテナ内と同一です。 ubuntu$ ls -ln /tmp/docker/normal/ total 0 -rw------- 1 0 0 0 May 18 15:53 without_user -rw------- 1 1000 1000 0 May 18 15:54 with_user_1000 -rw------- 1 2000 2000 0 May 18 15:54 with_user_2000 # Dockerホスト側でファイルを読めるかどうか確認します。 # →ubuntuユーザでは0:0のファイルを読むことができません。 ubuntu$ cat /tmp/docker/normal/without_user cat: /tmp/docker/normal/without_user: Permission denied # →ubuntuユーザでは1000:1000のファイルを読むことができます。 ubuntu$ cat /tmp/docker/normal/with_user_1000 # →ubuntuユーザでは2000:2000のファイルを読むことができません。 ubuntu$ cat /tmp/docker/normal/with_user_2000 cat: /tmp/docker/normal/with_user_2000: Permission deniedLinux版Dockerの通常モードでは、割と素直な結果になりました。整理すると以下の通りです。
--user
オプションを指定しない場合:
- Dockerコンテナ内での実行UID/GIDは
0:0
となり、ファイルのUID/GIDも同一となる。- また、Dockerホスト側から見てもファイルのUID/GIDはDockerコンテナ内と同一となる。
--user
オプションを指定した場合:
- Dockerコンテナ内での実行UID/GIDは
--user
オプションで指定した値となり、ファイルのUID/GIDも同一となる。- また、Dockerホスト側から見てもファイルのUID/GIDはDockerコンテナ内と同一となる。
3. Docker Desktop for Mac
続いてmacOS上で動作する「Docker Desktop for Mac」での挙動を調査しました。
3.1. 環境
調査した環境は以下の通りです。
- ハードウェア: MacBook Pro 2018
- OS: macOS 10.14.6(Mojave)
- Docker Desktop for Mac: 2.3.0.2
- Docker Engine: 19.03.8
mac$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G103 mac$ docker version Client: Docker Engine - Community Version: 19.03.8 API version: 1.40 Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:21:11 2020 OS/Arch: darwin/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.8 API version: 1.40 (minimum version 1.12) Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:29:16 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: v1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683 mac$ docker info ... Security Options: seccomp Profile: default ...なお、以下のコマンドは作業用のユーザ(UID
501
、GID20
)で実行しています。mac$ id -u 501 mac$ id -g 203.2. 実験
実験は以下の通りです。インラインで説明を記載しています。
# 実験用のディレクトリを作成します。(他ユーザでも書き込める必要があります) mac$ mkdir -p -m 777 /tmp/docker/mac # UID/GIDを指定せず、idコマンドを実行してUID/GIDを確認します。 # →UID/GIDは0:0です。 mac$ docker container run --tty --rm ubuntu:20.04 id uid=0(root) gid=0(root) groups=0(root) # UID/GIDを指定せず、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは0:0です。 mac$ docker container run --tty --rm --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/without_user && ls -ln /out/without_user" -rw------- 1 0 0 0 May 18 15:23 /out/without_user # UID/GIDとして1000:1000を指定し、idコマンドを実行してUID/GIDを確認します。 # →UID/GIDは1000:1000です。 mac$ docker container run --tty --user 1000:1000 ubuntu:20.04 id uid=1000 gid=1000 groups=1000 # UID/GIDとして1000:1000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは1000:1000です。 mac$ docker container run --tty --user 1000:1000 --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_1000 && ls -ln /out/with_user_1000" -rw------- 1 1000 1000 0 May 18 15:24 /out/with_user_1000 # UID/GIDとして2000:2000を指定し、idコマンドを実行してUID/GIDを確認します。 # →UIG/GIDは2000:2000です。 mac$ docker container run --tty --user 2000:2000 ubuntu:20.04 id uid=2000 gid=2000 groups=2000 # UID/GIDとして2000:2000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。 # →ファイルのUID/GIDは2000:2000です。 mac$ docker container run --tty --user 2000:2000 --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_2000 && ls -ln /out/with_user_2000" -rw------- 1 2000 2000 0 May 18 15:24 /out/with_user_2000 # Dockerホスト側のファイルのUID/GIDを確認します。 # →UID/GIDはDockerコンテナ内と異なり、UIDはすべてmacOS側の実行ユーザ、GIDはすべて0です。 mac$ ls -ln /tmp/docker/mac/ total 0 -rw------- 1 501 0 0 May 19 00:24 with_user_1000 -rw------- 1 501 0 0 May 19 00:24 with_user_2000 -rw------- 1 501 0 0 May 19 00:23 without_user # Dockerホスト側でファイルを読めるかどうか確認します。 # →UIDが同一のため、すべてのファイルはエラーなく読むことができます。 mac$ cat /tmp/docker/mac/without_user mac$ cat /tmp/docker/mac/with_user_1000 mac$ cat /tmp/docker/mac/with_user_2000Docker Desktop for Macでは、Linux版Dockerの通常モードとは異なる結果になりました。整理すると以下の通りです。
--user
オプションを指定しない場合:
- Dockerコンテナ内での実行UID/GIDは
0:0
となり、ファイルのUID/GIDも同一となる。(Linux版通常モードと同一の挙動)- ただし、Dockerホスト側から見たファイルのUID/GIDは
macOS側のUID:0
となる。(Linux版通常モードと異なる挙動)--user
オプションを指定した場合:
- Dockerコンテナ内での実行UID/GIDは指定した値となり、ファイルのUID/GIDも同一となる。(Linux版通常モードと同一の挙動)
- ただし、Dockerホスト側から見たファイルのUID/GIDは
macOS側のUID:0
となる。(Linux版通常モードと異なる挙動)4. 最後に
本命はRootlessモード、userns-remapモードの挙動の調査なので、頑張って続きを書きたいと思います。
参考