お勉強のため、AWSにkubeadmでKubernetesをインストールしたメモ。
CentOS 7.5
Docker CE 17.03.2
Kubernetes 1.11.0
前提
スクラッチでインストールする方法やその他いろいろ方法があるが、kubeadmを使ってインストールする。
Creating a single master cluster with kubeadm
Master1台とNode2台の計3台構成でインストールすることにする。
各マシンの要件としては2GBのメモリと2コアとあるが、インスタンスタイプはt2.smallにする(CPU数が要件に達していないので本当はt2.mediumにした方がよい)。
役割 | ホスト名 | インスタンスタイプ |
---|---|---|
Master | k8s-master | t2.small |
Node | k8s-node1 | t2.small |
Node | k8s-node2 | t2.small |
クラスター作成の流れ
Dockerやkubeadmなどのインストールについては、MasterとNodeで共通なので、
- MasterノードにDocker、kubeadm、kubelet、kubectlなどをインストール
- Masterノードのマシンを複製してNodeのマシンを2台作成
- kubeadmによるクラスターの作成
という流れで実施する。
EC2インスタンスの作成の事前準備
VPCの作成
(参考)
0から始めるAWS入門①:VPC編
AWSコンソールでVPCを作成する。
項目 | 値 |
---|---|
名前タグ | k8s-vpc(任意) |
IPv4 CIDRブロック | 10.0.0.0/16 |
IPv6 CIDRブロック | なし |
テナンシー | デフォルト |
サブネットの作成
作成したVPCにサブネットを作成する。
項目 | 値 |
---|---|
名前タグ | k8s-subnet(任意) |
VPC | (前のステップで作成したVPC) |
アベイラビリティーゾーン | 指定なし |
IPv4 CIDRブロック | 10.0.0.0/24 |
インターネットゲートウェイの作成
インターネットゲートウェイを作成する。
作成したインターネットゲートウェイをVPCにアタッチする。
ルートテーブルの追加
このままではまだインターネットから接続できないので、ルートテーブルを修正する。作成したVPCへのルートテーブルを選択し、設定を追加して保存する。
送信先 | ターゲット |
---|---|
0.0.0.0/0 | (前のステップで作成したインターネットゲートウェイ) |
セキュリティグループの作成
作成したVPCにセキュリティグループを作成する。
インバウンドのルールで、セキュリティグループ内の接続を許可するルールと、SSHを許可する。
Masterマシンの作成
EC2インスタンスの作成
(参考)
0から始めるAWS入門②:EC2編
EC2インスタンスを作成する。
AMIは「CentOS 7 (x86_64) - with Updates HVM」を使用する。
インスタンスタイプはt2.smallにする。
事前に作成したVPCを指定する。グローバルIPの自動割り当てを有効にする。
項目 | 値 |
---|---|
ネットワーク | (事前に作成したVPC) |
サブネット | (事前に作成したサブネット) |
自動割り当てパブリックIP | 有効化 |
ストレージはどれくらい必要かわからないが、40GBで作成。
Nameタグを追加しておく。
事前に作成したセキュリティグループを指定する。
設定を確認して、作成する。
OS更新
作成したインスタンスにSSHログインし、OSを更新する。
$ sudo yum update
hostname変更
以下のリンク先を参考に、ホスト名を更新する。
hostnamectlコマンドでホスト名を更新する。
$ sudo hostnamectl set-hostname --static k8s-master
再起動時にcloud.initでホスト名が変更されないようにするため、/etc/cloud/cloud.cfg
を編集し、
$ sudo vi /etc/cloud/cloud.cfg
以下を追加する。
preserve_hostname: true
/etc/hosts
でホスト名が解決できるようにしておく。
$ sudo vi /etc/hosts
10.0.0.33 k8s-master
一度再起動して、ログインし直す。
$ sudo reboot
Dockerのインストール
CentOS標準でインストール可能なDockerはバージョンが古いので、Docker公式の手順に従ってDocker CEをインストールする。
前提パッケージをインストールする。
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
Docker CEのリポジトリをセットアップする。
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
インストール可能なバージョンを確認する。
$ sudo yum list docker-ce --showduplicates | sort -r
推奨となっている17.03を指定してインストールする。バージョンを指定する場合は、同時にdocker-ce-selinuxも指定して--setopt=obsoletes=0
オプションを指定してあげる必要がある。
(参考)
https://github.com/docker/for-linux/issues/20
https://github.com/moby/moby/issues/33930
$ sudo yum install -y --setopt=obsoletes=0 docker-ce-17.03.2.ce docker-ce-selinux-17.03.2.ce
DockerデーモンをOS起動時に自動起動するよう設定し、起動する。
$ sudo systemctl enable docker && sudo systemctl start docker
kubeadmのインストール
以下の手順に従いkubeadmをインストールする。
ここからはrootで作業する。
$ sudo -i
リポジトリを追加する。
# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
SELinuxを無効化する。
# setenforce 0
再起動後もSELinuxが無効になるように設定する。
# vi /etc/sysconfig/selinux
#SELINUX=enforcing
SELINUX=permissive
kubelet、kubeadm、kubectlをインストールする。
# yum install -y kubelet kubeadm kubectl
kubeletデーモンをOS起動時に自動起動するよう設定し、起動する。
# systemctl enable kubelet && systemctl start kubelet
net.bridge.bridge-nf-call-iptables
が1
であることを確認する。1
であったので特に何もしない。
[root@k8s-master ~]# sysctl -n net.bridge.bridge-nf-call-iptables
1
[root@k8s-master ~]#
cgroup driverが一致しているかを確認する。しかしcgroup-driver
がそもそも定義されていないように見えるので、何もしない。
[root@k8s-master ~]# docker info | grep -i cgroup
Cgroup Driver: cgroupfs
[root@k8s-master ~]# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
# Note: This dropin only works with kubeadm and kubelet v1.11+
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/sysconfig/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
[root@k8s-master ~]#
v1.11の新機能であるIPVSによるロードバランシングを使用できるようにするため、必要なカーネルモジュールをロードする。
# modprobe -v ip_vs
# modprobe -v ip_vs_rr
# modprobe -v ip_vs_wrr
# modprobe -v ip_vs_sh
再起動後もカーネルモジュールがロードされるようにする。
/etc/modules-load.d/ip_vs.conf
を作成し、モジュールを列挙する。
# vi /etc/modules-load.d/ip_vs.conf
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
ここまでで前提ソフトウェアのインストールは完了なので、一旦マシンを停止する。
# shutdown -h now
Nodeマシンの作成
AMIイメージの作成
AWSコンソールで、MasterマシンからAMIを作成する。
EC2インスタンスの作成
作成したAMIから2台のEC2インスタンスを作成する。
- インスタンスタイプの選択
- t2.smallを選択
- インスタンス詳細の設定
- 事前に作成したVPCを指定
- 事前に作成したサブネットを指定
- 自動割り当てパブリックIPを有効化
- ストレージの追加
- 40GBの汎用SSDをルートに割り当て
- タグの追加
- Name=k8s-node1/2を追加
- セキュリティグループの設定
- 事前に作成したセキュリティグループを選択
以下の作業は2台のNodeマシンそれぞれに対して実施する。
ホスト名の変更
(Node1)
$ sudo hostnamectl set-hostname --static k8s-node1
(Node2)
$ sudo hostnamectl set-hostname --static k8s-node2
/etc/hosts
を修正する。この作業はMasterに対しても実施する。
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
10.0.0.33 k8s-master
10.0.0.129 k8s-node1
10.0.0.84 k8s-node2
kubeadmによるクラスターの作成
マシンの前提準備が整ったので、ここからkubeadmでクラスターの作成を行う。以下の手順に従う。
Masterの初期化
Masterマシンで、Pod network add-on(今回はcalicoを使う)に応じたcidrを指定して初期化する。
(k8s-masterで実施)
$ sudo -i
# kubeadm init --pod-network-cidr=192.168.0.0/16
[root@k8s-master ~]# kubeadm init --pod-network-cidr=192.168.0.0/16
I0629 07:56:25.611847 2184 feature_gate.go:230] feature gates: &{map[]}
[init] using Kubernetes version: v1.11.0
[preflight] running pre-flight checks
I0629 07:56:25.643552 2184 kernel_validator.go:81] Validating kernel version
I0629 07:56:25.643612 2184 kernel_validator.go:96] Validating kernel config
[preflight/images] Pulling images required for setting up a Kubernetes cluster
[preflight/images] This might take a minute or two, depending on the speed of your internet connection
[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[preflight] Activating the kubelet service
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [k8s-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.0.0.33]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Generated etcd/ca certificate and key.
[certificates] Generated etcd/server certificate and key.
[certificates] etcd/server serving cert is signed for DNS names [k8s-master localhost] and IPs [127.0.0.1 ::1]
[certificates] Generated etcd/peer certificate and key.
[certificates] etcd/peer serving cert is signed for DNS names [k8s-master localhost] and IPs [10.0.0.33 127.0.0.1 ::1]
[certificates] Generated etcd/healthcheck-client certificate and key.
[certificates] Generated apiserver-etcd-client certificate and key.
[certificates] valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[controlplane] wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests"
[init] this might take a minute or longer if the control plane images have to be pulled
[apiclient] All control plane components are healthy after 43.002143 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.11" in namespace kube-system with the configuration for the kubelets in the cluster
[markmaster] Marking the node k8s-master as master by adding the label "node-role.kubernetes.io/master=''"
[markmaster] Marking the node k8s-master as master by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-master" as an annotation
[bootstraptoken] using token: s7cx0z.3bjs1lz5vyv7v5im
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
Your Kubernetes master has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join 10.0.0.33:6443 --token s7cx0z.3bjs1lz5vyv7v5im --discovery-token-ca-cert-hash sha256:1f84300a56b03c40ba9f6048e5d8e6ac0c8ccea77849c42b4c548a1f1abc784f
[root@k8s-master ~]#
コンソールに出力されたkubeadm join
コマンドは後で使うのでコピーしておく。
なお、エラーになったとかでkubeadmコマンドをやり直したい場合はkubeadm reset
でリセット可能。
kubectlのセットアップ
一般ユーザーで以下を実行する。
(k8s-masterで実施)
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
もし、rootユーザーでkubectlを実行する場合は以下のコマンドで環境変数をセットする。
# export KUBECONFIG=/etc/kubernetes/admin.conf
Pod network add-onのインストール
Pod network add-onはcalicoを使う。以下の手順の「Installing with the Kubernetes API datastore—50 nodes or less」に従う。
$ kubectl apply -f \
https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
$ kubectl apply -f \
https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
ノードがReadyになるのを待つ。
[centos@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 13m v1.11.0
[centos@k8s-master ~]$
Nodeをクラスターに追加
各ノードで先ほど表示されたkubeadm join
コマンドをrootユーザーで実行する。
(k8s-node1/k8s-node2で実施)
[root@k8s-node1 ~]# kubeadm join 10.0.0.33:6443 --token s7cx0z.3bjs1lz5vyv7v5im --discovery-token-ca-cert-hash sha256:1f84300a56b03c40ba9f6048e5d8e6ac0c8ccea77849c42b4c548a1f1abc784f
[preflight] running pre-flight checks
I0629 08:13:03.564865 2178 kernel_validator.go:81] Validating kernel version
I0629 08:13:03.564968 2178 kernel_validator.go:96] Validating kernel config
[discovery] Trying to connect to API Server "10.0.0.33:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://10.0.0.33:6443"
[discovery] Requesting info from "https://10.0.0.33:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "10.0.0.33:6443"
[discovery] Successfully established connection with API Server "10.0.0.33:6443"
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.11" ConfigMap in the kube-system namespace
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[preflight] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-node1" as an annotation
This node has joined the cluster:
* Certificate signing request was sent to master and a response
was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the master to see this node join the cluster.
[root@k8s-node1 ~]#
[root@k8s-node2 ~]# kubeadm join 10.0.0.33:6443 --token s7cx0z.3bjs1lz5vyv7v5im --discovery-token-ca-cert-hash sha256:1f84300a56b03c40ba9f6048e5d8e6ac0c8ccea77849c42b4c548a1f1abc784f
[preflight] running pre-flight checks
I0629 08:13:51.375427 2155 kernel_validator.go:81] Validating kernel version
I0629 08:13:51.375528 2155 kernel_validator.go:96] Validating kernel config
[discovery] Trying to connect to API Server "10.0.0.33:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://10.0.0.33:6443"
[discovery] Requesting info from "https://10.0.0.33:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "10.0.0.33:6443"
[discovery] Successfully established connection with API Server "10.0.0.33:6443"
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.11" ConfigMap in the kube-system namespace
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[preflight] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-node2" as an annotation
This node has joined the cluster:
* Certificate signing request was sent to master and a response
was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the master to see this node join the cluster.
[root@k8s-node2 ~]#
追加したノードがReadyになったことを確認する。
[centos@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 18m v1.11.0
k8s-node1 Ready <none> 2m v1.11.0
k8s-node2 Ready <none> 1m v1.11.0
[centos@k8s-master ~]$
以上でクラスターの作成は完了。
稼働確認
Nginxのコンテナをデプロイしてアクセスしてみる。
Nginxのデプロイ
以下のyamlファイルを作成する。簡単のためサービスはNodePortで公開する。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
リソースを作成する。
$ kubectl apply -f nginx-deployment.yaml
$ kubectl apply -f nginx-service.yaml
Podが稼働していることを確認する。
[centos@k8s-master ~]$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-67594d6bf6-wbtck 1/1 Running 0 42s 192.168.2.6 k8s-node2
nginx-67594d6bf6-zdbff 1/1 Running 0 42s 192.168.1.5 k8s-node1
[centos@k8s-master ~]$
サービスのポートを確認する。
[centos@k8s-master ~]$ kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d <none>
nginx NodePort 10.110.164.204 <none> 80:31123/TCP 7m app=nginx
[centos@k8s-master ~]$
セキュリティグループの修正
外からのアクセスをSSHしか許可していなかったので、AWSコンソールでセキュリティグループのインバウンドルールを編集し、確認したServiceのポート(上記例では31123)へのアクセスを許可する。
ブラウザでいずれかのノードの上記のポートアクセスすると、Nginxの画面が表示される。
以上で稼働確認は終了。
(参考)ノードをクラスターから除去
例えば、k8s-node2ノードをクラスターから除去する場合は以下の手順を実施する。
Masterノードで以下を実行。
$ kubectl drain k8s-node2 --delete-local-data --force --ignore-daemonsets
$ kubectl delete node k8s-node2
[centos@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 13h v1.11.0
k8s-node1 Ready <none> 12h v1.11.0
k8s-node2 Ready <none> 12h v1.11.0
[centos@k8s-master ~]$ kubectl drain k8s-node2 --delete-local-data --force --ignore-daemonsets
node/k8s-node2 cordoned
WARNING: Ignoring DaemonSet-managed pods: calico-node-z5mxp, kube-proxy-vcs9q
[centos@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 13h v1.11.0
k8s-node1 Ready <none> 12h v1.11.0
k8s-node2 Ready,SchedulingDisabled <none> 12h v1.11.0
[centos@k8s-master ~]$ kubectl delete node k8s-node2
node "k8s-node2" deleted
[centos@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 13h v1.11.0
k8s-node1 Ready <none> 12h v1.11.0
[centos@k8s-master ~]$
Node側ではkubeadm reset
を実施する。
# kubeadm reset
[root@k8s-node2 ~]# kubeadm reset
[reset] WARNING: changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.
[reset] are you sure you want to proceed? [y/N]: y
[preflight] running pre-flight checks
[reset] stopping the kubelet service
[reset] unmounting mounted directories in "/var/lib/kubelet"
[reset] removing kubernetes-managed containers
[reset] cleaning up running containers using crictl with socket /var/run/dockershim.sock
[reset] failed to list running pods using crictl: exit status 1. Trying to use docker instead[reset] no etcd manifest found in "/etc/kubernetes/manifests/etcd.yaml". Assuming external etcd
[reset] deleting contents of stateful directories: [/var/lib/kubelet /etc/cni/net.d /var/lib/dockershim /var/run/kubernetes]
[reset] deleting contents of config directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
[root@k8s-node2 ~]#