Kubernetesの上での開発で役に立つコマンド

目的

  • GKE, EKSなどのKubernetesの上で開発する際に役に立つコマンドを紹介します

 

 

コマンド達

  • 内部で他のアプリケーションへ疎通確認等で利用する
  • curlを実行できるpodを作成して、シェルにアタッチする

```

 kubectl run  -it curl --image=radial/busyboxplus:curl --rm --restart=Never -- /bin/sh

```

 

  • portforward
  • kubernetes上のpod, svcをローカルにport fowardする
  • kubernetes上のサーバー等にローカルからアクセスして動作確認したい時に使います。

```

kubectl port-forward svc/nginx 8080:80

```

 

  • kubernetes上の既存のリソースや、コマンドで生成するリソースからyamlを生成する
  • リソースをyamlから変更したい時等に使います。

```

  • kubectl run busybox --image=busybox --dry-run=client -o yaml --restart=Never > yamlfile.yaml

```

 

 

protocol buffersの紹介

 protocol buffersとは

Googleが開発した高性能で効率的なRPC(Remote Procedure Call)フレームワークであるgRPCのIDLです。

protocol buffersの利点

  • 効率かつ軽量
    • JSON等よりも小さいデータ形式で表現でき、ストリーム処理も実装されているため、ネットワーク帯域の消費を抑えることができる
  • 言語と独立している
    • 言語やプラットフォームに依存しないフォーマットのため、マイクロサービス等の複数サービス間での通信が簡単に実現できます。(PHPなど一部の言語ではサーバー側は実装しにくいなどはあります)
  • コードの自動生成
    • 公式が公開しているツールで各言語での実装を自動生成できます。
  • セキュリティや開発に便利な機能
    • 認証や暗号化などのセキュリティ機能を提供しています。
      • TLSやTokenを用いたよりきめ細かい認証など
  • ヘルスチェックや、メトリクスの生成などプロダクション環境で必須となる機能を提供しています。

shellcodeの実装と書式文字列攻撃

shellcodeの実装

 

shellcodeを実行できるようにするために、何らかの文字列を関数として実行する必要が在る。

文字列の配列を準備

文字列の配列を関数ポインタにキャストする

その関数を実行する

関数ポインタ型 (void(*) ())

 

 

printf関数は第一引数に指定されたフォーマット指定子の数だけスタックを走査し、該当のアドレスのデータを出力する。フォーマット指定子を増やすことで、スタックの中身を自由に見ることができ、書式文字列攻撃と呼ぶ。

 

プログラミング言語のメモリ管理について

最近勉強したプログラミング言語のメモリ管理について紹介する。

参考にした本*1

そもそもなぜメモリ管理が必要なのか

  • 不要なメモリ領域を開放する。必要な時にメモリ領域を割り当てることをメモリ管理と呼ぶ
  • 不要なメモリが開放されないと、例えばwebサーバーのような長期間動くプロセスでは、利用するメモリがどんどん増えていき、コンピュータ自身の動作を不安定にする。メモリリークと呼ばれる
  • 必要なメモリ領域が確保されていない(例えば、開放された後に参照してしまう)と、何が起こるかわからない状態になる。エラーで落ちてくれるかもしれないし、アクセス許可をしていない値にアクセスしてしまうかも知れない。

 

代表的なメモリ管理方法とその詳細

  • 静的変数
    • プログラムを開始する前に必要なメモリ領域をすべて決めてしまう。
    • すべての変数がプログラムの開始から終了まで生存する
    • メリット
      • すべての変数のメモリが確保されており、開放されることもないのでメモリ管理の最低限の目的が達成される
    • デメリット
      • プログラム起動後にメモリ容量を変えることができない
  • 自動変数
    • 変数にスコープを設定して、それに従いメモリ領域を開放する
      • メリット
      • デメリット
        • 開放された後のメモリ領域にアクセスしてしまうことが在る
  • メモリの手動確保、開放
    • C,C++ 
    • プログラム作成者が任意のタイミングでメモリの確保、開放を行う
    • メリット
      • メモリの確保、開放のタイミング、その大きさを自由に決めることができる
    • デメリット
      • 管理が複雑。間違って2回開放してしまった場合などにプログラムをクラッシュさせてしまう。
      • ヒープを使うので、実行コスト大きい
  • スマートポインタ
    • C++
    • 参照カウントなど、既存のポインタにメモリ管理に有用な機能をもたせたポインタのこと。
    • メモリ管理に関する危険性を低減が期待できる。
    • 例えば、自分への参照が無くなった時に自動でメモリを開放することにより メモリ解放を手動でしなくても良くなっている 
      • メリット
        • 自動変数と手動確保の良いとこどりで、自由に確保、開放のタイミングを決めることができ、メモリリークなどが起きる可能性は低くなっている。
      • デメリット
        • 危険性が完全に無くなるわけではなく、例えば循環参照が起きると、開放されなくなる
        • 手動と同様にヒープを利用するので実行時コストは高くなる
  •  ガベージコレクション
    • Java, Go, Python
    • 自動で必要、不要を判断してメモリの確保開放を行ってくれる
    • メリット
      • 明示的に確保、開放を支持しなくても良い
    • デメリット
      • 必要、不要の判断に実行時コストがかかる
      • プログラム作成者がコントロールできないプログラム(ガベージコレクタ)が動くことになる
  • Rustのメモリ管理
    • Rust
    • 以下の3つのルールをしいている
      • 変数への参照が、参照先の値よりも長生きしない(参照先のスコープの外で使われない)ようにしている
      • 値を代入すると所有権も渡される(代入元の変数は使えなくなってしまう)
      • 可変参照は1つしか存在できない
    • メリット
      • GCとは異なり実行時に負荷がかからない方法でメモリ管理の危険性を(完全ではないが)回避している
      • プログラム自体のパフォーマンスが期待できる
      •  
    • デメリット
      • 複数の可変参照の作成にコストがかかる
        • できないことはないが、実行時オーバーヘッドがかかる
      • unsafe句以外でも、メモリリークが起きるような記述ができる

まとめ

色々なメモリ管理方法があるが、システムプログラムをかくならRustの方が他の言語よりもメンテナンス性、パフォーマンスの面で1段階上にいるとおもう。

 

*1:Software Design 2021年9月号

ページングについてわからなかった所とその現時点での理解

ページングテーブルについて理解が曖昧だった所の理解が進んだのでその記録*1

 

 

曖昧だった点

  • なぜ、ページングテーブルを多段に分けるのか?
  • 多段に分けても、同じアドレス幅で扱えるページ量は変わらないのでは?

現時点での理解

  • 多段に分けるのは、ページングテーブル自体に仮想記憶を適用するため
    • ページングテーブルを多段に分けることで
  • 扱えるページ量を増やすためではない

 

そもそもページングとは

メモリを区分けして何らかの属性を付与することで、プロセスごとでメモリ空間を扱いやすくしたり、仮想記憶を実現している。

しかし、扱えるメモリの最小単位である1バイト単位で属性を設定し、管理することは難しいので、一定の大きさに分けて管理している。

その区分け方法として、任意の大きさに分けるセグメント方式、一定の大きさに分けて必要な分だけ切り貼りするのがページング方式。

ページング方式ではプロセスのアクセスの局所性を考慮することで、必要な分だけメモリにロードする仮想記憶の仕組みを導入することができ、メモリを効率よく利用できる。この方法はデマンドページングと呼ばれる。

 

ページと実際の物理メモリの対応関係を記したものをページテーブルと呼ばれる。

ページテーブルにアクセする際は、CPU内のCR3(コントロールレジスタ3)の内容を取りだす。そこにはページテーブルが格納されている場所を表す、ページディレクトリテーブルが格納されている。

ここでアクセスされるページテーブルが通常のメモリと同様にページとして扱われ、仮想記憶を用いて効率よく利用される。

 

PTE : Page Table Entryページングに用いられる、データ構造。ページの場所を表す値や、書き込み、読み込みの履歴を管理するための値も格納されている

 

TLB : Translation Lookaside Buffer メモリアクセスの度にPTEを読み込んでいると遅くなるので、CPUに搭載されているPTE専用のキャッシュがありそれをTLBという。PTEが読み込まれた際に同時にTLBにコピーを置いておく。

 

 

*1:参考にした書籍 32ビットコンピュータをやさしく語る はじめて読む486アスキー書籍

OSレベルでのI/Oの制限方法

486でどのようにI/Oコントロールが行わているか。*1

 

486 ではIOPLビットでIOコントロールをするが、全部禁止、全部許可しかできない。

TSSの中にI/O許可マップというものを準備して、各ポートについての権限を管理する。

 

また、リアルモード用のアプリケーションをプロテクトモードで動かしても、直接IOポートにアクセスするので、他のプロセスとの連携がとれず、排他制御などに困る。

よってI/Oの仮想化をOS側が準備して、全てのI/Oへの信号をコントロールする

 

 

IOPL : I/O Privilege Level, CPUのEFLAGSレジスタの中のIOPLフィールドに設定するもの。I/Oにアクセスする際は動作しているプロセスの権限と、I/Oポートの権限レベル(IOPL)が比較され、プロセスの特権レベルがIOPLより小さい場合に限りあくせすができるようにする。

 

 

*1:参考にした書籍 32ビットコンピュータをやさしく語る はじめて読む486アスキー書籍

関数呼び出しがどうやって実装されているか

 

アセンブラでの関数呼び出しは以下のように実装されていることが多い。

呼び出す側の関数をA、呼び出される側の関数をBとする

例えば、

int sum(int a, int b){

  return a+b

}

void main(){

   sum(1,2)

}

ならmainがA, sumがBを指す

  1. 引数をスタックに積む
  2. Aに戻った際に次に実行すべき命令が格納されているアドレスをスタックに積む
  3. Aを実行していたときのベースレジスタを積む
  4. ebpを退避しておいて、ebpにespを代入する
  5. ebpを基準に引数を取得する(例えばこの時、途中で関数呼び出しがあっても、ebp+4で引数が呼べる)
  6. 関数から戻る時はこれと逆の処理をする