Slide 1

Slide 1 text

Container Runtime Meetup #6 Podman最新情報 (2024年冬) Manabu Ori Red Hat 2024-12-16 1 v1.1

Slide 2

Slide 2 text

2 ▸ 名前: 織 学 (@orimanabu) ▸ 所属: Red Hat ▸ 仕事: OpenShiftのコンサルティング 自己紹介

Slide 3

Slide 3 text

3 Podman update

Slide 4

Slide 4 text

4 ▸ https://github.com/containers/podman ▸ コンテナを構築、管理、実行する次世代のコンテナエンジン ▸ Open Container Initiative(OCI)を標準フォーマットとして採用 ▸ Dockerとの互換性(コマンドライン、 Dockerイメージ、Compose、Docker互換API、など) ▸ Red Hatの開発チームを中心に OSSで開発 → CNCF Sandboxプロジェクトに申請 ▸ Fedora/CentOS/RHEL 系 Linux ディストリビューションに同梱 ・ RHEL8 以降、OS 同梱のコンテナエンジンが Docker から Podman に変更(RHEL 8以降は Docker非サポート) ・ Red Hat 製品の中ではPodmanが多く使われる ▸ Linuxだけでなく、macOS、Windowsでも使用可能 Podmanとは podman desktop

Slide 5

Slide 5 text

5 ▸ デーモンレス ▸ Fork & Execモデル ▸ ルートレス実行前提の設計 ▸ systemdとの連携 ▸ Kubernetesと互換のあるPod ▸ Podmanの関連ツール(Buildah、Skopeo) ▸ etc… Podmanの特徴

Slide 6

Slide 6 text

6 ▸ 2024-03 GA ▸ 互換性のない破壊的なAPI変更いろいろ ▸ cgroups v1の非推奨化 (v6で非サポート予定) ▸ BoltDBからSQLiteへの変更 ・ 過去バージョンからのアップグレード時は引き続きBoltDBを使用 ▸ Podman Machine ・ macOS: Apple Hypervisor, libkrun (Qemuのサポートはなし) ・ v5.1からAppleHVでRosetta2をサポート ・ v5.2からlibkrunのサポート ・ Windows: Hyper-V, WSL2 ▸ Podman v5

Slide 7

Slide 7 text

7 ▸ Podmanはローカルでコンテナを操作するための CLI ▸ CRI-OはKubernetesから呼び出されるCRIランタイム ※ PodmanとCRI-Oは直接の依存関係はありません Podman, CRI-Oと仲間たち podman CRI-O buildah skopeo containers/image containers/storage crun, runc, youki conmon netavark aardvark-dns containers/common

Slide 8

Slide 8 text

8 ▸ [Sandbox] Podman Desktop #308 ・ https://github.com/podman-desktop ▸ [Sandbox] Podman Container Tools #309 ・ https://github.com/containers/podman ・ https://github.com/containers/buildah ・ https://github.com/containers/skopeo ・ https://github.com/containers/netavark ・ https://github.com/containers/aardvark-dns ・ https://github.com/containers/image ・ https://github.com/containers/storage ・ https://github.com/containers/common ・ https://github.com/containers/conmon ・ https://github.com/containers/podman-py ▸ [Sandbox] bootc #310 ・ https://github.com/containers/bootc ▸ [Sandbox] composefs #311 ・ https://github.com/containers/composefs ▸ (CRI-Oは既にGraduated https://www.cncf.io/projects/cri-o/) Podmanの仲間たちがCNCF Sadboxにapplyしました

Slide 9

Slide 9 text

9 ▸ Pasta ▸ composefs+zstd:chunked ▸ libkrun 今日のお題

Slide 10

Slide 10 text

10 Passt/Pasta

Slide 11

Slide 11 text

11 ▸ https://passt.top ▸ Slirp / Slirp4netnsの代替となるOSSプロジェクト ・ Slirp or Passt : QemuからのEthnernet Frame(AF_UNIX socket)をホストのL4ソケットに変換 ・ Slirp4netns or Pasta : network namespaceに接続したTAPデバイスからのEthernet frameをL4ソ ケットに変換 ▸ Passt、Pastaは同じバイナリ ▸ Podman v5.0から、rootless時のデフォルトのユーザーモードネットワーキングツール ▸ 参考文献 ・ KVM Forum 2022: Slirp is Dead, Long Live Slirp! A New Approach to User-mode Networking ・ Everything Open 2023: passt & pasta: Modern unprivileged networking for containers and VMs ・ DevConf.cz 2023: Root is less: container networks get in shape with pasta Passt / Pasta

Slide 12

Slide 12 text

12 ▸ NATが必要 ・ Kubevirt ▸ セキュリティ (attack surfaceが比較的大きい) ・ ちゃんとした(?) TCP/IPスタック ・ Port Redirection機能 (FTP, IRC, RealAudo, ...) ▸ パフォーマンスがいまいち ・ コードベースがとても古い ・ もともとダイアルアップのスピードが出ればよかった ・ TCP Window Scalingのサポートがない ▸ IPv6サポートが限定的 ・ NDP, DHCPv6が使えない ・ ポートのマッピングができない Slirpの課題点

Slide 13

Slide 13 text

13 ▸ 2020年にプロジェクト開始 ▸ セキュリティを重視 ・ 最小限のコードベース (~15,000 LOC) ・ 外部依存はglibcのみ ・ NATは最小限 ・ 動的メモリ確保はしない (sbrk(2), brk(2), mmap(2)は使わない) ・ seccompで使用するシステムコールを制限 ・ x86_64の場合、passtで30個、pastaで41個のシステムコールのみ使用 ・ namespaceを切り離して実行 ・ user, mount, ipc, uts, pid namespace ・ 空のtmpfsにpivot_root ・ CapabilityはCAP_NET_BIND_SERVICE以外はdrop ・ SELinux/AppArmorプロファイルの提供 ▸ Passt / Pastaの特徴 (1) $ cloc . 181 text files. 119 unique files. 63 files ignored. github.com/AlDanial/cloc v 2.02 T=0.08 s (1470.2 files/s, 388950.2 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C 42 3152 5201 12896 Bourne Shell 19 663 574 2944 C/C++ Header 43 485 1189 1950 Markdown 3 270 10 810 make 3 88 37 312 diff 1 32 136 294 YAML 2 25 48 146 Text 2 61 0 83 HTML 4 0 0 76 ------------------------------------------------------------------------------- SUM: 119 4776 7195 19511 -------------------------------------------------------------------------------

Slide 14

Slide 14 text

14 ▸ TCPステートを簡略化 ・ CLOSED, SYN_RCVD, ESTABLISHED ▸ TCP Windows Scalingのサポート ▸ バッチシステムコール (sendmmsg, recvmmsg)のサポート ▸ チェックサム計算における AVX2のサポート (x86_64) ▸ シングルキューのTAPとほぼ同等のパフォーマンス ・ マルチキューのTAPよりは遅い ▸ IPv6のフルサポート ・ NDP, DHCPv6 ▸ Passt / Pastaの特徴 (2)

Slide 15

Slide 15 text

15 ▸ passtで使えるシステムコール ・ accept4 bind clock_gettime close connect epoll_ctl epoll_pwait epoll_wait exit_group fallocate fcntl ftruncate getsockopt listen lseek read recvfrom recvmmsg recvmsg sendmmsg sendmsg sendto setsockopt shutdown socket timerfd_create timerfd_gettime timerfd_settime write writev ▸ pastaで使えるシステムコール ・ accept4 bind clock_gettime clone close connect epoll_ctl epoll_pwait epoll_wait exit exit_group fallocate fcntl ftruncate getsockopt ioctl listen lseek openat pipe2 read recvfrom recvmmsg recvmsg rt_sigprocmask rt_sigreturn sendmmsg sendmsg sendto setns setsockopt shutdown socket splice timerfd_create timerfd_gettime timerfd_settime waitid write writev (ご参考) seccompでフィルタしているシステムコール

Slide 16

Slide 16 text

16 zstd:chunked

Slide 17

Slide 17 text

17 ▸ OCI image specと互換性のある新しいイメージレイヤーフォーマット ・ メディアタイプ tar+zstd をサポートするツールであれば透過的に使用可能 ▸ Google CRFSからインスパイアされたフォーマット ・ (同じくCRFSを元にした) eStargz と似た考え方 ・ メタデータのフォーマット等、開発時はアドバイスをいろいろいただきました ▸ eStargzとの比較 ・ stargz-snapshotterにもzstdサポートは入っており、 zstdでのlazy pullingも可能 ・ Podman自体にはlazy pullingの機能はないが、stargz-snapshotterをAdditional Layer Storeとし て使用することでPodmanでもlazy pullingできる ▸ 設定 ・ storage.confの[storage.options]セクション zstd:chunked pull_options = {enable_partial_images = "true", use_hard_links = "false", ostree_repos=""} ▸ 詳しくはここに書きました ・ https://zenn.dev/orimanabu/articles/zstd-chunked-intro

Slide 18

Slide 18 text

18 ▸ コンテナレイヤーの末尾に TOC (Table of Contents)等のメタデータを付与 ▸ レイヤーをpullするときの流れ ・ まずTOCをダウンロード ・ image manifestのアノテーションにオフセットが載っている ・ TOCにリストされたファイルごとに ・ 取得済みの全レイヤーの TOCを見て、もしそのファイルを取得済みであれば、手元のファ イルからハードリンク or reflink or コピーを作成する ・ 手元になければ、HTTP Range Requestを使ってファイル単位でダウンロードする zstd:chunked

Slide 19

Slide 19 text

19 ▸ ストレージの効率化 ・ reflink: i-nodeがわかれるので、ページキャッシュの共通化はできない ・ ハードリンク: dedupする前のメタデータが異なったとしても保持できない (mtime, link count等) zstd:chunked Without reflink With reflinks

Slide 20

Slide 20 text

20 ▸ ネットワークの効率化 zstd:chunked

Slide 21

Slide 21 text

21 ▸ 準備 ・ index.htmlだけ異なるapache httpdのコンテナイメージを 10個作成 ・ --squash-all でベースイメージ含めてひとつのレイヤーに押し込める 実験 FROM fedora:41 RUN dnf install -y httpd && dnf clean all RUN echo "ServerName localhost" >> /etc/httpd/conf/httpd.conf && mkdir -p /var/www/html ARG N RUN echo "Container #${N}" > /var/www/html/index.html EXPOSE 80 CMD ["httpd", "-D", "FOREGROUND"] for i in $(seq 0 9); do podman build . -t quay.io/manabu.ori/myhttpd:c${i} --build-arg N=${i} --squash-all podman push --compression-format=zstd:chunked quay.io/manabu.ori/myhttpd:c${i} done

Slide 22

Slide 22 text

22 ▸ pull 実験 $ time podman pull quay.io/manabu.ori/myhttpd:c0 Trying to pull quay.io/manabu.ori/myhttpd:c0... Getting image source signatures Copying blob 4ca6d99209cf done 81.2MiB / 81.2MiB (skipped: 42.4KiB = 0.05%) Copying config ddb744a7a7 done | Writing manifest to image destination 284abe58dc8218d85fa8c7d5b111149b8b57061fb2d89ca2130d44c70f6199bb real 0m8.611s user 0m2.785s sys 0m1.015s $ time podman pull quay.io/manabu.ori/myhttpd:c1 Trying to pull quay.io/manabu.ori/myhttpd:c1... Getting image source signatures Copying blob 63df9f9dafda done 81.2MiB / 81.2MiB (skipped: 79.4MiB = 97.87%) Copying config 0ca3f4f445 done | Writing manifest to image destination 9abebd0ec4a754fb65e2ab985c89b71b9fa339a23606e0288d6526e78a4d47dd real 0m6.728s user 0m0.719s sys 0m1.109s

Slide 23

Slide 23 text

23 ▸ reflinkの状態を確認するのは難しい ... 実験 $ filefrag -ek ~/.local/share/containers/storage/overlay/09a82655f255757ec1d03b8d81b9cd92f0ffcf987d31c2b7ef1f0f5205777d77/diff/usr/bin/ls Filesystem type is: 9123683e File size of /home/ori/.local/share/containers/storage/overlay/09a82655f255757ec1d03b8d81b9cd92f0ffcf987d31c2b7ef1f0f5205777d77/diff/usr/bin/ls is 145480 (144 blocks of 1024 bytes) ext: logical_offset: physical_offset: length: expected: flags: 0: 0.. 127: 2438128.. 2438255: 128: encoded,shared 1: 128.. 143: 2424244.. 2424259: 16: 2438256: last,encoded,shared,eof /home/ori/.local/share/containers/storage/overlay/09a82655f255757ec1d03b8d81b9cd92f0ffcf987d31c2b7ef1f0f5205777d77/diff/usr/bin/ls: 2 extents found $ sudo btrfs filesystem du -s ~/.local/share/containers/storage/overlay/* Total Exclusive Set shared Filename 218.59MiB 11.46MiB 120.48MiB /home/ori/.local/share/containers/storage/overlay/09a82655f255757ec1d03b8d81b9cd92f0ffcf987d31c2b7ef1f0f5205777d77 209.22MiB 0.00B 120.48MiB /home/ori/.local/share/containers/storage/overlay/c66d4f5eccdb07b29d460fa0c23af76e222a9ae97aca9134ac55370e4c466130 0.00B 0.00B 0.00B /home/ori/.local/share/containers/storage/overlay/l 0.00B 0.00B 0.00B /home/ori/.local/share/containers/storage/overlay/staging

Slide 24

Slide 24 text

24 ▸ ハードリンクはわかりやすい 実験 $ podman image ls REPOSITORY TAG IMAGE ID CREATED SIZE quay.io/manabu.ori/myhttpd c9 4e5d2e31e232 3 days ago 227 MB quay.io/manabu.ori/myhttpd c8 89bffbd7f7f2 3 days ago 227 MB quay.io/manabu.ori/myhttpd c7 9a2a54fe127c 3 days ago 227 MB quay.io/manabu.ori/myhttpd c6 294570b6402f 3 days ago 227 MB quay.io/manabu.ori/myhttpd c5 dc9f54791668 3 days ago 227 MB quay.io/manabu.ori/myhttpd c4 ce5114de2d6c 3 days ago 227 MB quay.io/manabu.ori/myhttpd c3 163518020f87 3 days ago 227 MB quay.io/manabu.ori/myhttpd c2 38fb1cd4cb8c 3 days ago 227 MB quay.io/manabu.ori/myhttpd c1 9abebd0ec4a7 3 days ago 227 MB quay.io/manabu.ori/myhttpd c0 284abe58dc82 3 days ago 229 MB $ sudo du -csh ~/.local/share/containers/storage/overlay/* 230M /home/ori/.local/share/containers/storage/overlay/00db55db807cab800fdf6b6ed39ec46e8ec53c1e4b755893f9f6d30d500fe4d0 15M /home/ori/.local/share/containers/storage/overlay/09a82655f255757ec1d03b8d81b9cd92f0ffcf987d31c2b7ef1f0f5205777d77 11M /home/ori/.local/share/containers/storage/overlay/30fec452bc3836be305803c342af9f11a5ca6e16f38496176b6c80bee168ae69 11M /home/ori/.local/share/containers/storage/overlay/36fc922787f2a9132ba3d7ea9decc8db6203518c6e05a61a049b91f6c3c66f81 11M /home/ori/.local/share/containers/storage/overlay/480110807cafc058472a3f2eea4d8f9a57bc0a528d6ae21108bf05475e4d28c7 11M /home/ori/.local/share/containers/storage/overlay/79241936613eb087b6b84a3939bbbadcb0bb198b089c65c90e916f9b06688123 11M /home/ori/.local/share/containers/storage/overlay/c66d4f5eccdb07b29d460fa0c23af76e222a9ae97aca9134ac55370e4c466130 11M /home/ori/.local/share/containers/storage/overlay/f1674671929ad75874ecd2bf1fc394ee394a1802acafcb899c7a703c194c7cb7 11M /home/ori/.local/share/containers/storage/overlay/f401afca4b05f6a3dfa89dd0caf6f7cbc5c3a8706d1f42084ddabac370b7ba88 11M /home/ori/.local/share/containers/storage/overlay/f72b3dfebd3aca217b7f0d5ef0a75e50567a696faafd058b1176cd00ef80f418 4.0K /home/ori/.local/share/containers/storage/overlay/l 0 /home/ori/.local/share/containers/storage/overlay/staging 328M total

Slide 25

Slide 25 text

25 ▸ メモリ使用量 ・ ハードリンクの場合 実験 $ smem -t -P '^sleep' PID User Command Swap USS PSS RSS 467575 ori sleep infinity 0 92 239 1556 467379 ori sleep infinity 0 96 243 1560 467477 ori sleep infinity 0 96 244 1560 467346 ori sleep infinity 0 96 253 1552 467411 ori sleep infinity 0 92 260 1640 467539 ori sleep infinity 0 96 264 1644 467508 ori sleep infinity 0 96 268 1624 467445 ori sleep infinity 0 100 272 1628 467314 ori sleep infinity 0 96 291 1708 467280 ori sleep infinity 0 120 304 1660 ------------------------------------------------------------------------------- 10 1 0 980 2638 16132 $ smem -t -P '^sleep' PID User Command Swap USS PSS RSS 466252 ori sleep infinity 0 1420 1420 1428 466283 ori sleep infinity 0 1504 1504 1512 466153 ori sleep infinity 0 1508 1508 1516 466220 ori sleep infinity 0 1512 1512 1520 466314 ori sleep infinity 0 1512 1512 1520 466414 ori sleep infinity 0 1512 1512 1520 466189 ori sleep infinity 0 1596 1596 1604 466381 ori sleep infinity 0 1600 1600 1608 466121 ori sleep infinity 0 1640 1640 1648 466347 ori sleep infinity 0 1656 1656 1664 ------------------------------------------------------------------------------- 10 1 0 15460 15460 15540 ・ reflinkの場合

Slide 26

Slide 26 text

26 composefs

Slide 27

Slide 27 text

27 ▸ "The reliability of disk images, the flexibility of files" ・ マウントできる ・ ファイルベースの柔軟さ ▸ ある種のループバックファイルシステム ・ composefs = 通常のファイルシステム上のファイルを erofs + overlayfsでマウントする ・ ファイルツリー+メタデータ: erofsのイメージファイル ・ データ: オブジェクトストアに Content Addressableな形で配置 ・ ファイル名 = そのファイルのSHA256ハッシュ ▸ fs-verityを使ったファイルシステム全体の改ざん検知 ・ 通常、fs-verity自体はファイルデータのみに対して有効 (メタデータやパスの変更は検知できな い) ・ composefsでは、erofsイメージにもfs-verityを有効化するとファイルシステム全体の改ざん検知 が可能 composefsとは

Slide 28

Slide 28 text

28 ▸ mkcomposefs ・ オブジェクトストアに「ファイル名が SHA256ハッシュ値のファイルを作成 ・ ファイルツリー/メタデータをerofsイメージファイルとして作成 実験 + example.xfs (erofsイメージファイル) $ mkcomposefs --digest-store=objects rootfs example.cfs sudo mount -t composefs -o basedir=objects example.cfs /mnt/composefs

Slide 29

Slide 29 text

29 ▸ erofsイメージ内のファイルの拡張属性に、リダイレクト先のオブジェクトストア内のパスを保存 実験 $ sudo getfattr -d -m - /mnt/erofs/foo.txt getfattr: Removing leading '/' from absolute path names # file: mnt/erofs/foo.txt security.selinux="unconfined_u:object_r:user_home_t:s0" trusted.overlay.metacopy=0sACQAAYXWANRi9cNzi1XD6/VwwxJjNT3GqjVEjGqPmqUZQpyK trusted.overlay.redirect="/85/d600d462f5c3738b55c3ebf570c31263353dc6aa35448c6a8f9aa519429c8a"

Slide 30

Slide 30 text

30 ▸ Podmanでは、composefsの利用はzstd:chunkedが前提 ・ zstd:chunkedのハードリンクはいい点がいろいろあったが、課題点もあった ・ composefsとzstd:chunkedを組み合わせると、ハードリンクでも問題なし! ・ 各レイヤーのメタデータは erofsに正しく保持される ▸ ただし、現状composefsの利用はrootfulモードのみ ・ erofsがloopbackデバイスを使用するため? (loopbackデバイスはnamespaceに優しくない) ・ 絶賛対応中 ・ better loopback handling (hiding it) #144 ・ unprivileged/rootless composefs #2099 ・ erofsに最近入ったこれが救世主かも ・ erofs: add file-backed mount support ▸ Podmanでは、composefsの利用はzstd:chunkedが前提 ▸ 詳しくはここに書きました ・ https://zenn.dev/orimanabu/articles/composefs-intro Podmanとcomposefs

Slide 31

Slide 31 text

31 libkrun

Slide 32

Slide 32 text

32 ▸ Apple Hypervisor ・ Virtualization.frameworkベースの仮想化 ・ Qemuよりは安定していた ・ 課題点 ・ virtio-fsの機能が足りない/バグが直らない (UID mapping, SELinux周り, ...) ・ GPUパススルーできない ・ 「ソースコード見れないし ...」(←OSS原理主義者の主張 ▸ Podman Machineをlibkrunベースにして解決! ・ 詳しくはここに書きました ・ https://zenn.dev/orimanabu/articles/rosetta-libkrun Podman on macOSの課題

Slide 33

Slide 33 text

33 ▸ プロセスを仮想環境内で実行できるようにするライブラリ ・ https://github.com/containers/libkrun ・ https://kvmforum2021.sched.com/event/ke36/libkrun-more-than-a-vmm-in-dynamic-libr ary-form-sergio-lopez-pascual-red-hat ▸ Kataよりも軽量、最小限の virtioデバイス ・ virtio-console ・ virtio-vsock (specialized for TSI, Transparent Socket Impersonation) ・ virtio-fs ・ virtio-baloon (only free-page reporting) ・ virtio-rng ・ virtio-block (for AMD SEV) ・ virtio-net ・ Kataよりも軽量、最小限の virtioデバイス ▸ もともとはKVM専用だったが、後に Apple Hypervisor.frameworkにも対応 ・ Virtualization.frameworkは使用しない libkrunとは

Slide 34

Slide 34 text

34 GPUパススルー drm: virtio-gpu virtio-gpu virgl venus OpenGL app Vulkan app vrend vkr drm: Guest Userspace Guest Kernel (Linux) Host Userspace Host Kernel (Linux) Mesa virglrenderer Qemu ホストOSがLinuxの場合 drm: virtio-gpu venus Vulkan app Guest Userspace Guest Kernel (Linux) Host Userspace Host Kernel (XNU) vkr virglrenderer virtio-gpu libkrun-efi MortenVK Metal Mesa Vulkan API Vulkan API Venus Protocol Metal API Vulkan API Vulkan API Venus Protocol OpenGL API OpenGL API OpenGL commands (Gallium IR) ホストOSがmacOSの場合

Slide 35

Slide 35 text

35 ▸ libkrunでRosetta2は使えるか? ▸ 実は昔は使えていた、が revertされた ▸ rosetta実行時、謎のioctlを/proc/self/exeに発行する ▸ これをエミュレートすれば、 libkrunでもRosetta実行が可能 ▸ 詳細はここに書きました ・ https://zenn.dev/orimanabu/articles/rosetta-libkrun libkrunとRosetta2 ori@myfedora:~/work$ strace /mnt/rosetta/rosetta execve("/mnt/rosetta/rosetta", ["/mnt/rosetta/rosetta"], 0xfffff242ddf0 /* 31 vars */) = 0 openat(AT_FDCWD, "/proc/self/exe", O_RDONLY) = 3 ioctl(3, _IOC(_IOC_READ, 0x61, 0x22, 0x45), 0xffffc32340e8) = 1 close(3) = 0 gettid() = 29760 awrite(2, "Usage: rosetta Optional environment variables: ROSETTA_DEBUGSERVER_PORT wait for a debugger connection on given port version: Rosetta-318.8 ) = 165 exit(1) = ? +++ exited with 1 +++

Slide 36

Slide 36 text

36 ▸ Docker Desktop 4.23から選択可能な仮想化方式 ・ https://www.docker.com/blog/what-are-the-latest-docker-desktop-enterprise-grade-per formance-optimizations/ ・ https://docs.docker.com/desktop/features/vmm/#docker-vmm-beta ▸ なんかlibkrunって書いてある... ・ https://github.com/docker/for-mac/issues/7464#issuecomment-2446688074 ▸ 確認したところ、ほんとに libkrunを使っていました。 ところでDocker VMMってご存知ですか

Slide 37

Slide 37 text

linkedin.com/company/red-hat youtube.com/user/RedHatVideos facebook.com/redhatinc twitter.com/RedHat 37 Red Hat is the world’s leading provider of enterprise open source software solutions. Award-winning support, training, and consulting services make Red Hat a trusted adviser to the Fortune 500. Thank you