九州大学のSECKUN 2021/ProSec-IT(enPiT-Pro) 2021の共通カリキュラムにおいて、近藤 @udzura が担当したコンテナ概要の授業にて使用した教材です。
今回、公益性を鑑み、授業固有の連絡事項などを削除した状態で公開します。
ライセンスは Creative Commons Attribution 4.0 International Public License (CC BY 4.0) ref とします。 個人の自学、社内研修、スクールでの授業などでお使いいただけますが、内容は無保証です。
- それぞれの手順に従い、環境を構築し、手を動かしながら実践してください。
- 途中の「演習」については、ぜひご自身で答えを考えてください。
- VirtualBox
- Vagrant
Macの方は「OS X/Mac OS X」のリンク、Windowsの方(講師は基本的にWindowsをサポートできません、ご留意ください)は「Windows」のリンクからそれぞれインストーラを落とせるでしょう。
Linuxデスクトップの方: もしいらした場合、次の項 0-2) に一気にスキップして大丈夫です。
(重要)MacOS Monterey ではVirtualBoxが動作しないという報告がありました。 QEMU ベースである Lima であれば動くかもしれませんので、その手順を下記「0-1') Limaのセットアップ」に残してあります。筆者はMonterey環境がまだなく、こちらも動くとは限りません。M-1 Macの方と同様にパブリッククラウド上でLinuxのVM(x86-64)を構築することもご検討ください。
(重要)いわゆるM-1 Mac の方: VirtualBoxが動作しません。以下の方法での実践となりますが、サポートできません。
- 後述の「0-1') Limaのセットアップ」を試してみる。サポート外です。
- 職場や大学上のネットワーク、もしくはパブリッククラウド上でLinuxのVM(x86-64)を構築し、その中で演習を行う。ポートフォワーディングなどが必要な箇所は自力で考える。
- UTM、もしくはQEMUを直接利用してVMを作成し、その中で演習を行う。参考情報のみ共有となります。
- 「Ubuntu Weekly Recipe 第672回 UTMを使ってM1 Mac上でUbuntuを動かす」ほか https://gihyo.jp/admin/serial/01/ubuntu-recipe/0672
以下のようなVagrantfileを作成し、環境を立ち上げましょう。
Vagrant.configure("2") do |config|
config.vm.box = "generic/ubuntu2004"
config.vm.box_check_update = true
config.vm.synced_folder ".", "/vagrant"
config.vm.network "forwarded_port", guest: 8080, host: 8080
# もし、ホストマシンの 8080 ポートを使っている場合、別の指定をしても構いません
# config.vm.network "forwarded_port", guest: 8080, host: 18080
config.vm.provider "virtualbox" do |vb|
vb.cpus = 4
vb.memory = (1024 * 4).to_s
end
config.vm.provision "shell", inline: <<-SHELL
apt-get -y update
SHELL
end
立ち上げのためのコマンドです。時間がかかります。
// Mac
$ vagrant up
// Win
PS> vagrant up
// Mac
$ vagrant ssh
// Win
PS> vagrant ssh
LimaはMacの場合homebrewでインストールできます。 ref: https://github.com/lima-vm/lima#installation
$ brew install lima
UbuntuのVMを作成します(講義で想定している Ubuntu 20.04 より新しいものが入りますが、問題ないはずです)。
$ curl -L https://raw.githubusercontent.com/lima-vm/lima/master/examples/ubuntu.yaml > seckun.yml
$ limactl start seckun.yml
別のターミナルで sudo tail -f $HOME/.lima/seckun/serial.log
とすることでシリアルコンソールのログが見られます。起動時間が長い場合にお試しください。
今回は、Limaで立ち上げたマシンには以下のコマンドで入ることを推奨します(VirtualBoxなどのSSH経由接続に合わせたい)。
$ eval "$(limactl show-ssh seckun)"
その他の操作は、公式ドキュメントや limactl help
などを参照してください。これ以降、Linuxにログイン後は、手順は同じです。
なお、ポートフォワーディングは Guest 8080 -> Host 8080 を自動で行うそうです。
ホストの設定によって、名前解決がうまくいかなくなるパターンがあるようです。例えば /etc/resolv.conf
を以下のような内容に変更すると解決する一時的に場合があります。 /etc/resolv.conf
はOSの設定によっては自動で上書きされてしまうのでご留意ください。
nameserver 8.8.8.8
以下の手順では、すべてのコマンドは「Linux上のターミナルで」実行します。
- See: https://docs.docker.com/engine/install/ubuntu/
- Install using the repository の手順に従ってください。
- 以下のようなコマンドで、以下のような表示が出ればOKです。
vagrant@ubuntu2004:~$ sudo docker run hello-world
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/
# Rubyのインストール
# aptのものではなくSNAPのものを使います。3.0のはずです。
$ sudo apt remove ruby2.7 rubygems-integration
$ sudo snap install ruby --classic
以下のような表示が出ることを想定しています。
$ ruby --version
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]
ここまでに、「Dockerのインストール」までを行ってください。
以下はRubyを使います(Pythonなどがいい方は、ご自身で同等のアプリケーションを考えて提案してもOKです。勉強させていただければと)。 「Rubyのインストール」を終えてください。
# プロジェクト作成
$ mkdir ~/seckun-app
$ cd ~/seckun-app
$ bundle init
# Gemfileを編集
gem "webrick"
gem "sinatra"
# app.rb を作成、以下に編集
require "sinatra"
get "/" do
"Hello, from Docker. My Ruby version is: #{RUBY_VERSION}"
end
$ bundle install --path vendor/bundle
$ bundle exec ruby app.rb
演習 1-1 別のターミナルを開いてログインし、アクセス可能なことを確かめてください。
# Dockerfile.step1 という名前で作成
FROM ruby:3
CMD ["ruby", "--version"]
# (1)
$ sudo docker build -t seckun:sample1 -f Dockerfile.step1 .
$ sudo docker run -t seckun:sample1
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]
# (2)
$ sudo docker run -ti seckun:sample1 irb
irb(main):001:0> system 'uname -a'
Linux f7167eb6441f 5.4.0-33-generic #37-Ubuntu SMP Thu May 21 12:53:59 UTC 2020 x86_64 GNU/Linux
=> true
irb(main):002:0> exit
演習 1-2 (1) (2) で何が起こっているか説明してください。また、(1)でRubyの任意のプログラムや、外部スクリプトをコンテナ内部において実行させるにはどうすればいいですか?
# Dockerfile.step2 というファイルを作成
FROM ruby:3
RUN gem install webrick
EXPOSE 8080
CMD ["ruby", "-run", "-e", "httpd"]
$ sudo docker build -t seckun:sample2 -f Dockerfile.step2 .
$ sudo docker run -d -p8080:8080 seckun:sample2
28db047f8dc29979b1d8876211fb9e2db841f1c211e73e4fdbad8ce011e0eaff
## ブラウザで localhost:8080 にアクセスする
# ログの確認
$ sudo docker logs 28db047
[2021-10-27 10:13:01] INFO WEBrick 1.7.0
[2021-10-27 10:13:01] INFO ruby 3.0.2 (2021-07-07) [x86_64-linux]
[2021-10-27 10:13:01] INFO WEBrick::HTTPServer#start: pid=1 port=8080
10.0.2.2 - - [27/Oct/2021:10:13:12 UTC] "GET / HTTP/1.1" 200 3269
- -> /
[2021-10-27 10:13:12] ERROR `/favicon.ico' not found.
10.0.2.2 - - [27/Oct/2021:10:13:12 UTC] "GET /favicon.ico HTTP/1.1" 404 281
http://localhost:8880/ -> /favicon.ico
## 止めるには
$ sudo docker stop 28db047
$ sudo docker rm 28db047
演習 2-1
ps aufx
コマンドなどでRubyのプロセスが存在することを確認してください。これは、シェルでrubyコマンドを立ち上げた時とどう違いますか?- 今、ブラウザから見えているものは何なのか説明してください。
Tips: .dockerignore
というファイルで、イメージに送らないパスを指定できる。キャッシュのようなものは除外するのが吉
# e.g.
Dockerfile*
vendor/*
# Dockerfile.step3 を作成
FROM ruby:3
RUN mkdir /app
ADD . /app
WORKDIR /app
RUN gem install bundler
RUN bundle install --deployment --path vender/bundle
EXPOSE 4567
# "-o0.0.0.0" については発展課題で
CMD ["bundle", "exec", "ruby", "app.rb", "-o0.0.0.0"]
$ sudo docker build -t seckun:sample3 -f Dockerfile.step3 .
$ sudo docker run -d -p8080:4567 seckun:sample3
fc37f85c62e4ba9c61d9b710ea8694830aca51c0e0fe2046e02c6b9f0480b15e
## ブラウザで localhost:8080 にアクセスしよう
## アクセスログを確認
$ sudo docker logs fc37f85
[2021-10-27 10:15:12] INFO WEBrick 1.7.0
[2021-10-27 10:15:12] INFO ruby 3.0.2 (2021-07-07) [x86_64-linux]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2021-10-27 10:15:12] INFO WEBrick::HTTPServer#start: pid=1 port=4567
10.0.2.2 - - [27/Oct/2021:10:15:31 +0000] "GET / HTTP/1.1" 200 45 0.0047
10.0.2.2 - - [27/Oct/2021:10:15:31 UTC] "GET / HTTP/1.1" 200 45
- -> /
10.0.2.2 - - [27/Oct/2021:10:15:31 +0000] "GET /favicon.ico HTTP/1.1" 404 469 0.0007
10.0.2.2 - - [27/Oct/2021:10:15:31 UTC] "GET /favicon.ico HTTP/1.1" 404 469
http://localhost:8880/ -> /favicon.ico
- 次の演習に入る前にアプリケーションは必ず停止してください。
演習 2-2
- 演習2-1の回答を踏まえて、今、ブラウザやcurlでアクセスできるアプリケーションがどこにいるのか説明してください。
- Sinatraはデフォルトで
127.0.0.1
にバインドしますが、Dockerを利用した場合それだとホストからうまくアクセスできず、0.0.0.0
に明示的にバインド指定をする必要があります。それはなぜなのか考察してください - アプリケーションの中身を以下のように変えたい場合、どういう手順を踏めばいいか説明してください。
require "sinatra"
get "/" do
"Hello, from Docker. My Ruby version is: #{RUBY_VERSION}"
end
get "/hello" do
"This is a new contents."
end
ここまでの演習で作成したアプリケーションに以下のように手を加えたいと思います。
- アクセスカウンターを付与する
- アクセスカウンターのデータは、PostgreSQLに保存する
- Sequelというrubygemがあります。これを用いて簡易的なアクセスカウンターをトップページに実装します。
# Gemfileの変更
gem "webrick"
gem "sinatra"
gem "sequel"
gem "pg"
Gemfile があるディレクトリで、 bundle install
をしなおします。
$ sudo apt install build-essential libpq-dev
$ bundle install
その後、アプリケーションを変更します。
# app.rb の変更
require "sinatra"
require "sequel"
require "pg"
require "logger"
DB = Sequel.postgres(ENV['POSTGRES_DB'],
user: ENV['POSTGRES_USER'],
password: ENV['POSTGRES_PASSWORD'],
host: ENV['POSTGRES_HOST'],
port: 5432,
max_connections: 10,
logger: Logger.new(STDOUT))
unless DB.table_exists?(:counters)
DB.create_table :counters do
primary_key :id
Integer :count
end
end
counters = DB[:counters]
if counters.count == 0
counters.insert(count: 0)
end
helpers do
def counters
DB[:counters]
end
def increment_count
counters.update(count: Sequel[:count] + 1)
end
def current_count
counter = counters.first
counter[:count]
end
end
get "/" do
increment_count
"Hello, from Docker.\n" +
"My Ruby version is: #{RUBY_VERSION}\n" +
"Access Count: #{current_count}\n"
end
# Dockerfile.app を作成
FROM ruby:3
RUN apt -y install libpq-dev
RUN gem install bundler
RUN mkdir /app
ADD . /app
WORKDIR /app
RUN bundle install --deployment --path vender/bundle
EXPOSE 4567
CMD ["bundle", "exec", "ruby", "app.rb", "-o0.0.0.0"]
$ sudo docker build -t seckun:app -f Dockerfile.app .
演習 3-1
- アプリケーションの実装上の変更点の概要を説明してください。
- Docker Compose をLinuxにインストールしてください。以下の手順が参考になるでしょう。
docker-compose.yml
を作成して、PostgreSQLをバックエンドにしたWebアプリケーションを立ち上げてください。- YAMLは以下を参考にしても構いません。
?????
には適切な値が入りますので考える・調査すること。
app:
image: seckun:app
command: bundle exec ruby app.rb -o0.0.0.0
ports:
- 8080:4567
environment:
- POSTGRES_DB=seckun
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=testpassw0rd
- POSTGRES_HOST=?????
links: ?????
db:
image: postgres:?????
environment:
- POSTGRES_PASSWORD=testpassw0rd
- POSTGRES_DB=seckun
- ヒント: PostgreSQL Imageには指定された環境変数を渡し、設定を行います
- 更なるヒント: Composeのクラスタの別のホストにはどうやってアクセスするでしょうか。
- 更なる更なるヒント: Docker Composeは起動順序を制御できます。
演習 3-2
- 完成したYAMLファイルの例を提示してください。
以下の内容は、今回は時間がないので授業ではやりません(すいません)。ご興味があれば手順をさらってみましょう!
(注意: 今回の演習の範囲ではありませんが、去年の手順をそのまま残します。細かい差異があると思いますが、勉強したい方は挑戦しても構いません)
- で作成したDocker Composeベースのアプリケーションは、Kubernetesのマニフェストに変更できます。
- インストールすべきもの
- minikubeのDockerドライバを利用する
- vagrantなど一般ユーザで
docker
コマンドが利用できる状態にしなければならない
- vagrantなど一般ユーザで
$ USER=udzura
$ sudo usermod -aG docker $USER && newgrp docker
$ minikube start --driver=docker
- インストールの成功を確認する
$ kubectl cluster-info
Kubernetes master is running at https://192.168.49.2:8443
KubeDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-f9fd979d6-sbxhg 1/1 Running 0 33s
kube-system etcd-minikube 0/1 Running 0 38s
kube-system kube-apiserver-minikube 1/1 Running 0 38s
kube-system kube-controller-manager-minikube 0/1 Running 0 37s
kube-system kube-proxy-fvjcj 1/1 Running 0 33s
kube-system kube-scheduler-minikube 0/1 Running 0 38s
kube-system storage-provisioner 1/1 Running 0 37s
- kompose のインストール
- Getting Started (https://github.com/kubernetes/kompose/blob/master/docs/getting-started.md) を参照して
kompose convert
以下を行う
$ kompose convert
INFO Kubernetes file "app-service.yaml" created
INFO Kubernetes file "app-deployment.yaml" created
INFO Kubernetes file "db-deployment.yaml" created
$ mkdir manifests; mv *.yaml manifests
- デプロイする。
$ kubectl apply -f manifests
- Podが作成されるので状態を確認する。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-6698dc95cb-kz7bx 0/1 ImagePullBackOff 0 53s
db-c495495dd-px9fq 1/1 Running 0 53s
ImagePullBackOff
であり、イメージが見えない状態。minikube VM上のdockerでイメージをビルドする
$ eval $(minikube docker-env)
$ docker ps # 変化しているのを確認
$ docker build -t seckun:app -f Dockerfile.app .
- 一度podを削除すると、deploymentの定義により再作成されるので、再確認する
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-6698dc95cb-4fmkk 0/1 Error 3 58s
db-c495495dd-px9fq 1/1 Running 0 5m19s
$ kubectl logs app-6698dc95cb-4fmkk
/app/vender/bundle/ruby/2.7.0/gems/sequel-5.38.0/lib/sequel/adapters/postgres.rb:210:in `initialize': PG::ConnectionBad: could not translate host name "db" to address: Name or service not known (Sequel::DatabaseConnectionError)
- 次はpostgresのホストの名前解決ができない。
- 本来はpostgresのサービスをKubernetesで定義すべきだが、今回は簡易的に現在のdbに振られたIPを指定する。
$ kubectl describe pods/db-c495495dd-px9fq
Name: db-c495495dd-px9fq
# ...
IP: 172.17.0.4
IPs:
IP: 172.17.0.4
$ vim manifests/app-deployment.yaml #...
$ kubectl apply -f manifests/app-deployment.yaml
deployment.apps/app configured
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-fdf986cc8-4nkdv 1/1 Running 0 10s
db-c495495dd-px9fq 1/1 Running 0 12m
- サービスは正しく立ち上がるのだが、実はこのままではアクセスができない。
- services/app の定義を変更する必要がある。
type: NodePort
としてexpose
apiVersion: v1
kind: Service
...
spec:
ports:
- name: "8080"
nodePort: 30080
port: 8080
targetPort: 4567
type: NodePort
selector:
io.kompose.service: app
...
$ kubectl apply -f manifests/app-service.yaml
service/app created
$ minikube service app --url
http://192.168.49.2:30080
- このURLはホストからアクセス可能。
$ curl http://192.168.49.2:30080/
Hello, from Docker.
My Ruby version is: 2.7.2
Access Count: 1
この後は、公式ドキュメントを参考に、マニフェストの定義を色々と変えて試してみよう。
考えてみてください。よろしければ、お答えを @udzura に教えてください。
- 午前の部分で特に興味が湧いた・印象に残ったトピックと、その背景を教えてください(140字以上、上限は何文字でも構いません)。
- 以下の3つのうちどれかを選択し答えてみましょう。
- Google Cloudの「Cloud Run」というサービスについて調査せよ。
- ここまでの演習で作成したDocker単体と、Docker Composeベースのアプリケーションについて、どのアプリケーションがCloud Runへのデプロイに向いているか考察せよ。その際、デプロイできるようにするにはどのような変更を行えばいいか示せ。
- 他のクラウドベンダー、PaaSで類似のサービスがないか調査しまとめよ。
- 演習 4) で掲示した手順を、最新のバージョンにキャッチアップしつつ実行せよ(注: 講師の手抜きではない...はず?)。
- 生成されたYAMLマニフェストをもとに、Kubernetesの基本的なリソース Pod、Service、Deployment についてそれぞれ、関係を含め説明せよ。
- 成果物となるKubernetesのマニフェストファイルや、そのほか関連するDockerfileなどを一通りプロジェクトにまとめ、Gitリポジトリにし、公開されたGitHubのリポジトリにpushして提出せよ。この際、機密情報などは各自で考慮すること。
- (高度です)minikubeで生成されたKubernetesクラスタは単一のノードしか持たないが、ノードが複数になった場合はどのように運用が変わるか答えよ。
- 「Linuxコンテナはただのプロセスと同じものだ」という主張に対して、根拠となる事実やLinuxの機能を挙げよ。また、もしこの主張に対する反論があればそれらも根拠を元に挙げよ。
- コンテナ型仮想化概論(川口直也, 2020, カットシステム)
- 近藤宇智朗, 森田浩平「コンテナのセキュリティを中身から理解しよう」 https://speakerdeck.com/udzura/inside-out-container-and-its-security
- 松本亮介「ユビキタスデータセンターOSの文脈におけるコンテナ実行環境の分類」 https://hb.matsumoto-r.jp/entry/2019/02/08/135354
- 徳永航平「今話題のいろいろなコンテナランタイムを比較してみた」 https://www.slideshare.net/KoheiTokunaga/ss-123664087
- 宮下剛輔「Compare OCI Runtimes」 https://speakerdeck.com/mizzy/compare-oci-runtimes
- 加藤泰文「LXCで学ぶコンテナ入門 - 軽量仮想化環境を実現する技術」 https://gihyo.jp/admin/serial/01/linux_containers/0001
- utam0k「詳説 OCIコンテナランタイム youki」 https://speakerdeck.com/utam0k/xiang-shuo-ocikontenarantaimu-youki-at-di-15hui-kontenaji-shu-falseqing-bao-jiao-huan-hui
- 「Large-scale cluster management at Google with Borg」 https://pdos.csail.mit.edu/6.824/papers/borg.pdf
- 日本語では「Googleが作った分散アプリケーション基盤、Borgの論文を読み解く」シリーズなど https://blog.inductor.me/entry/2019/10/30/010839
- 宮下剛輔「Infrastructure as Codeのこれまでとこれから」 https://speakerdeck.com/mizzy/infra-study-meetup-number-1