Wednesday, January 30, 2019

Replace Failed OSD in Rook Deployed Ceph


If you have been reading some of my recent articles on Rook you have seen how to install a Ceph cluster with Rook on Kubernetes. This article extends on that Kubernetes installation and discusses how to replace a failed OSD in the Ceph cluster.

First lets review our current running Ceph cluster observing the rook-ceph-system, rook-ceph and inside the toolbox the Ceph status:

# kubectl get pods --all-namespaces -o wide
NAMESPACE          NAME                                      READY   STATUS      RESTARTS   AGE    IP            NODE          NOMINATED NODE   READINESS GATES
kube-system        coredns-86c58d9df4-22fps                  1/1     Running     4          3d2h   10.244.3.55   kube-node3               
kube-system        coredns-86c58d9df4-jp2zb                  1/1     Running     6          3d2h   10.244.2.66   kube-node2               
kube-system        etcd-kube-master                          1/1     Running     3          3d5h   10.0.0.81     kube-master              
kube-system        kube-apiserver-kube-master                1/1     Running     3          3d5h   10.0.0.81     kube-master              
kube-system        kube-controller-manager-kube-master       1/1     Running     5          3d5h   10.0.0.81     kube-master              
kube-system        kube-flannel-ds-amd64-5m9x5               1/1     Running     6          3d5h   10.0.0.83     kube-node2               
kube-system        kube-flannel-ds-amd64-7xgf4               1/1     Running     3          3d5h   10.0.0.81     kube-master              
kube-system        kube-flannel-ds-amd64-dhdzm               1/1     Running     5          3d2h   10.0.0.84     kube-node3               
kube-system        kube-flannel-ds-amd64-m6fx5               1/1     Running     3          3d5h   10.0.0.82     kube-node1               
kube-system        kube-proxy-bnbzn                          1/1     Running     3          3d5h   10.0.0.82     kube-node1               
kube-system        kube-proxy-gjxlg                          1/1     Running     4          3d2h   10.0.0.84     kube-node3               
kube-system        kube-proxy-kkxdb                          1/1     Running     3          3d5h   10.0.0.81     kube-master              
kube-system        kube-proxy-knzsl                          1/1     Running     6          3d5h   10.0.0.83     kube-node2               
kube-system        kube-scheduler-kube-master                1/1     Running     4          3d5h   10.0.0.81     kube-master              
rook-ceph-system   rook-ceph-agent-748v8                     1/1     Running     0          103m   10.0.0.83     kube-node2               
rook-ceph-system   rook-ceph-agent-9vznf                     1/1     Running     0          103m   10.0.0.82     kube-node1               
rook-ceph-system   rook-ceph-agent-hfdv6                     1/1     Running     0          103m   10.0.0.81     kube-master              
rook-ceph-system   rook-ceph-agent-lfh7m                     1/1     Running     0          103m   10.0.0.84     kube-node3               
rook-ceph-system   rook-ceph-operator-76cf7f88f-qmvn5        1/1     Running     0          103m   10.244.1.65   kube-node1               
rook-ceph-system   rook-discover-25h5z                       1/1     Running     0          103m   10.244.1.66   kube-node1               
rook-ceph-system   rook-discover-dcm7k                       1/1     Running     0          103m   10.244.0.41   kube-master              
rook-ceph-system   rook-discover-t4qs7                       1/1     Running     0          103m   10.244.3.61   kube-node3               
rook-ceph-system   rook-discover-w2nv5                       1/1     Running     0          103m   10.244.2.72   kube-node2               
rook-ceph          rook-ceph-mgr-a-8649f78d9b-k6gwl          1/1     Running     0          100m   10.244.3.62   kube-node3               
rook-ceph          rook-ceph-mon-a-576d9d49cc-q9pm6          1/1     Running     0          101m   10.244.0.42   kube-master              
rook-ceph          rook-ceph-mon-b-85f7b6cb6b-pnrhs          1/1     Running     0          101m   10.244.1.67   kube-node1               
rook-ceph          rook-ceph-mon-c-668f7f658d-hjf2v          1/1     Running     0          101m   10.244.2.74   kube-node2               
rook-ceph          rook-ceph-osd-0-6f76d5cc4c-t75gg          1/1     Running     0          100m   10.244.2.76   kube-node2               
rook-ceph          rook-ceph-osd-1-5759cd47c4-szvfg          1/1     Running     0          100m   10.244.3.64   kube-node3               
rook-ceph          rook-ceph-osd-2-6d69b78fbf-7s4bm          1/1     Running     0          100m   10.244.0.44   kube-master              
rook-ceph          rook-ceph-osd-3-7b457fc56d-22gw6          1/1     Running     0          100m   10.244.1.69   kube-node1               
rook-ceph          rook-ceph-osd-prepare-kube-master-72kfz   0/2     Completed   0          100m   10.244.0.43   kube-master              
rook-ceph          rook-ceph-osd-prepare-kube-node1-jp68h    0/2     Completed   0          100m   10.244.1.68   kube-node1               
rook-ceph          rook-ceph-osd-prepare-kube-node2-j89pc    0/2     Completed   0          100m   10.244.2.75   kube-node2               
rook-ceph          rook-ceph-osd-prepare-kube-node3-drh4t    0/2     Completed   0          100m   10.244.3.63   kube-node3               
rook-ceph          rook-ceph-tools-76c7d559b6-qvh2r          1/1     Running     0          6s     10.0.0.82     kube-node1               

# kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash

# ceph status
  cluster:
    id:     edc7cac7-21a3-45ae-80a9-5d470afb7576
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum c,a,b
    mgr: a(active)
    osd: 4 osds: 4 up, 4 in
 
  data:
    pools:   0 pools, 0 pgs
    objects: 0  objects, 0 B
    usage:   17 GiB used, 123 GiB / 140 GiB avail
    pgs:     
 
# ceph osd tree  
ID CLASS WEIGHT  TYPE NAME            STATUS REWEIGHT PRI-AFF 
-1       0.13715 root default                                 
-5       0.03429     host kube-master                         
 2   hdd 0.03429         osd.2            up  1.00000 1.00000 
-4       0.03429     host kube-node1                          
 3   hdd 0.03429         osd.3            up  1.00000 1.00000 
-2       0.03429     host kube-node2                          
 0   hdd 0.03429         osd.0            up  1.00000 1.00000 
-3       0.03429     host kube-node3                          
 1   hdd 0.03429         osd.1            up  1.00000 1.00000 

At this point the Ceph cluster is clean and in a healthy state.  However I am going to introduce some chaos and which will cause osd1 to go down.  In my case since this is a virtual lab I am going to just kill the OSD process and clear out osd1 data to mimic a failed drive.

Now when we look at the cluster state in the toolbox we can see OSD1 is down:

# kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash

# ceph status
  cluster:
    id:     edc7cac7-21a3-45ae-80a9-5d470afb7576
    health: HEALTH_WARN
            1 osds down
            1 host (1 osds) down
 
  services:
    mon: 3 daemons, quorum c,a,b
    mgr: a(active)
    osd: 4 osds: 3 up, 4 in
 
  data:
    pools:   0 pools, 0 pgs
    objects: 0  objects, 0 B
    usage:   17 GiB used, 123 GiB / 140 GiB avail
    pgs:     
 
[root@kube-node1 /]# ceph osd tree
ID CLASS WEIGHT  TYPE NAME            STATUS REWEIGHT PRI-AFF 
-1       0.13715 root default                                 
-5       0.03429     host kube-master                         
 2   hdd 0.03429         osd.2            up  1.00000 1.00000 
-4       0.03429     host kube-node1                          
 3   hdd 0.03429         osd.3            up  1.00000 1.00000 
-2       0.03429     host kube-node2                          
 0   hdd 0.03429         osd.0            up  1.00000 1.00000 
-3       0.03429     host kube-node3                          
 1   hdd 0.03429         osd.1          down  1.00000 1.00000 

Given I removed the contents of the OSD lets go ahead and replace the failed drive. The first steps are to go into the toolbox and run the usual commands to remove a Ceph OSD from the cluster:

# kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash

# ceph osd out osd.1
marked out osd.1. 

# ceph osd crush remove osd.1
removed item id 1 name 'osd.1' from crush map

# ceph auth del osd.1
updated

# ceph osd rm osd.1
removed osd.1

Lets exit out of the toolbox and go back to the master node command line and delete the Ceph OSD 3 deployment:

# kubectl delete deployment -n rook-ceph rook-ceph-osd-1
deployment.extensions "rook-ceph-osd-1" deleted

Now would be the time to replace the physically failed disk. In my case the disk is still good I just simulated the failure by downing the OSD process and removing the data.

To get the new disk back into the cluster we only need to restart the rook-ceph-operator pod and we can do so in Kubernetes with the following scale deployment commands:

# kubectl scale deployment rook-ceph-operator --replicas=0 -n rook-ceph-system
deployment.extensions/rook-ceph-operator scaled

# kubectl get pods --all-namespaces -o wide|grep operator

# kubectl scale deployment rook-ceph-operator --replicas=1 -n rook-ceph-system
deployment.extensions/rook-ceph-operator scaled

# kubectl get pods --all-namespaces -o wide|grep operator
rook-ceph-system   rook-ceph-operator-76cf7f88f-g9pxr        0/1     ContainerCreating   0          2s              kube-node2               

When the rook-ceph-operator is restarted it will go through and re-run each rook-ceph-osd-prepare container which will scan the system it is on and look for any disks that should be incorporated into the cluster based on the original cluster.yaml settings when the Ceph cluster was deployed with Rook.  In this case it will see the new disk on kube-node-3 and incorporate that into OSD1.

We can confirm our assessment by seeing a new container for OSD1 was spawned and also by logging into the toolbox and running the familiar Ceph commands:

# kubectl get pods -n rook-ceph -o wide
NAME                                      READY   STATUS      RESTARTS   AGE     IP            NODE          NOMINATED NODE   READINESS GATES
rook-ceph-mgr-a-8649f78d9b-k6gwl          1/1     Running     0          110m    10.244.3.62   kube-node3    <none>           <none>
rook-ceph-mon-a-576d9d49cc-q9pm6          1/1     Running     0          110m    10.244.0.42   kube-master   <none>           <none>
rook-ceph-mon-b-85f7b6cb6b-pnrhs          1/1     Running     0          110m    10.244.1.67   kube-node1    <none>           <none>
rook-ceph-mon-c-668f7f658d-hjf2v          1/1     Running     0          110m    10.244.2.74   kube-node2    <none>           <none>
rook-ceph-osd-0-6f76d5cc4c-t75gg          1/1     Running     0          109m    10.244.2.76   kube-node2    <none>           <none>
rook-ceph-osd-1-69f5d5ffd-kndd7           1/1     Running     0          67s     10.244.3.68   kube-node3    <none>           <none>
rook-ceph-osd-2-6d69b78fbf-7s4bm          1/1     Running     0          109m    10.244.0.44   kube-master   <none>           <none>
rook-ceph-osd-3-7b457fc56d-22gw6          1/1     Running     0          109m    10.244.1.69   kube-node1    <none>           <none>
rook-ceph-osd-prepare-kube-master-n2t7g   0/2     Completed   0          79s     10.244.0.47   kube-master   <none>           <none>
rook-ceph-osd-prepare-kube-node1-ttznt    0/2     Completed   0          77s     10.244.1.72   kube-node1    <none>           <none>
rook-ceph-osd-prepare-kube-node2-9kxcl    0/2     Completed   0          75s     10.244.2.79   kube-node2    <none>           <none>
rook-ceph-osd-prepare-kube-node3-cpf4s    0/2     Completed   0          73s     10.244.3.66   kube-node3    <none>           <none>
rook-ceph-tools-76c7d559b6-qvh2r          1/1     Running     0          9m28s   10.0.0.82     kube-node1    <none>           <none>

# ceph status
  cluster:
    id:     edc7cac7-21a3-45ae-80a9-5d470afb7576
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum c,a,b
    mgr: a(active)
    osd: 4 osds: 4 up, 4 in
 
  data:
    pools:   0 pools, 0 pgs
    objects: 0  objects, 0 B
    usage:   17 GiB used, 123 GiB / 140 GiB avail
    pgs:     

# ceph osd tree 
ID CLASS WEIGHT  TYPE NAME            STATUS REWEIGHT PRI-AFF 
-1       0.13715 root default                                 
-5       0.03429     host kube-master                         
 2   hdd 0.03429         osd.2            up  1.00000 1.00000 
-4       0.03429     host kube-node1                          
 3   hdd 0.03429         osd.3            up  1.00000 1.00000 
-2       0.03429     host kube-node2                          
 0   hdd 0.03429         osd.0            up  1.00000 1.00000 
-3       0.03429     host kube-node3                          
 1       0.03429         osd.1            up  1.00000 1.00000 

As you can see replacing a failed OSD with Rook is about as uneventful as replacing a failed OSD in a standard deployed Ceph cluster.   Hopefully this demonstration provided the proof of that.

Further Reading:

Rook: https://github.com/rook/rook


Rook & Ceph on Kubernetes


In a previous article I wrote about using Rook to deploy a Ceph storage cluster within Minikube (link below). The original post described what Rook can provide and demonstrated the ease of quickly setting up an all in one Ceph cluster. However I wanted explore Rook further in a multi-node configuration and how it integrates with applications in Kubernetes.

First I needed to set up a base Kubernetes environment which consisted of 1 master and 3 worker nodes. I used the following steps on all nodes to prepare them for Kubernetes: add hostname to host files, disable Selinux and swap, enable br_netfilter, install supporting utilities, enable Kubernetes repo, install docker, install Kubernetes binaries and enable/disable relevant services.

# echo "10.0.0.81   kube-master" >> /etc/hosts
# echo "10.0.0.82   kube-node1" >> /etc/hosts
# echo "10.0.0.83   kube-node2" >> /etc/hosts
# echo "10.0.0.84   kube-node3" >> /etc/hosts
# setenforce 0
# sed -i --follow-symlinks 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux
# swapoff -a
# sed -i.bak -r 's/(.+ swap .+)/#\1/' /etc/fstab
# modprobe br_netfilter
# echo '1' > /proc/sys/net/bridge/bridge-nf-call-iptables
# echo 'br_netfilter' > /etc/modules-load.d/netfilter.conf
# echo net.bridge.bridge-nf-call-iptables=1 >> /etc/sysctl.d/10-bridge-nf-call-iptables.conf
# dnf install -y yum-utils device-mapper-persistent-data lvm2
# dnf install docker
# cat > /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
# dnf install -y kubelet kubeadm kubectl
# systemctl enable docker ; systemctl start docker ; systemctl enable kubelet ; systemctl start kubelet ; systemctl stop firewalld ; systemctl disable firewalld

Once the prerequisites are met on each node lets initialize the cluster on the master node:

# kubeadm init --apiserver-advertise-address=10.0.0.81 --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.13.2
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [kube-master localhost] and IPs [10.0.0.81 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [kube-master localhost] and IPs [10.0.0.81 127.0.0.1 ::1]
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [kube-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.0.0.81]
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 19.511836 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "kube-master" as an annotation
[mark-control-plane] Marking the node kube-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node kube-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: etmucm.238nrw6a48yu0njb
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[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.81:6443 --token etmucm.238nrw6a48yu0njb --discovery-token-ca-cert-hash sha256:963d6d9d31f2db9debfaa600ef802d05c448f7dc9e9cb92aec268cf2a8cfee7b

After the master is up and running you can join the remaining nodes using the following command which was presented in the output when you initialized the master:

# kubeadm join 10.0.0.81:6443 --token etmucm.238nrw6a48yu0njb --discovery-token-ca-cert-hash sha256:963d6d9d31f2db9debfaa600ef802d05c448f7dc9e9cb92aec268cf2a8cfee7b
[preflight] Running pre-flight checks
[discovery] Trying to connect to API Server "10.0.0.81:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://10.0.0.81:6443"
[discovery] Requesting info from "https://10.0.0.81: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.81:6443"
[discovery] Successfully established connection with API Server "10.0.0.81:6443"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] 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 "kube-node1" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver 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.

I like to do some housekeeping once all my nodes are joined which includes enabling scheduling on master and labeling the worker nodes as such:

# kubectl taint node kube-master node-role.kubernetes.io/master:NoSchedule-
# kubectl label node kube-node1 node-role.kubernetes.io/worker=worker
# kubectl label node kube-node2 node-role.kubernetes.io/worker=worker
# kubectl label node kube-node3 node-role.kubernetes.io/worker=worker
 
Once you have joined the nodes you should have a cluster that looks like this:

# kubectl get nodes
NAME          STATUS   ROLES    AGE   VERSION
kube-master   Ready    master   19h   v1.13.2
kube-node1    Ready    worker   19h   v1.13.2
kube-node2    Ready    worker   19h   v1.13.2
kube-node3    Ready    worker   17h   v1.13.2
 
Next lets deploy Flannel for networking:

# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

And finally lets deploy Rook and Ceph cluster using the familiar steps from my previous article:

# git clone https://github.com/rook/rook.git
# cd ./rook/cluster/examples/kubernetes/ceph
# sed -i.bak s+/var/lib/rook+/data/rook+g cluster.yaml
# kubectl create -f operator.yaml
# kubectl create -f cluster.yaml
# kubectl create -f toolbox.yaml
 
Once all the containers have spun up you should have something that looks like the following:
 
# kubectl get pod -n rook-ceph -o wide
NAME                                      READY   STATUS      RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
rook-ceph-mgr-a-8649f78d9b-txsfm          1/1     Running     1          19h   10.244.2.12   kube-node2               
rook-ceph-mon-a-598b7bd4cd-kpxnx          1/1     Running     0          19h   10.244.0.3    kube-master              
rook-ceph-mon-c-759b8984f5-ggzjb          1/1     Running     1          19h   10.244.2.15   kube-node2               
rook-ceph-mon-d-77d55dcddf-mwnf8          1/1     Running     0          16h   10.244.3.3    kube-node3               
rook-ceph-osd-0-77b448bbcc-mdhsw          1/1     Running     1          19h   10.244.2.14   kube-node2               
rook-ceph-osd-1-65db4b7c5d-hgfcj          1/1     Running     0          16h   10.244.1.8    kube-node1               
rook-ceph-osd-2-5b475cb56c-x5w6n          1/1     Running     0          19h   10.244.0.5    kube-master              
rook-ceph-osd-3-657789944d-swjxd          1/1     Running     0          16h   10.244.3.6    kube-node3               
rook-ceph-osd-prepare-kube-master-tlhxf   0/2     Completed   0          16h   10.244.0.6    kube-master              
rook-ceph-osd-prepare-kube-node1-lgtrf    0/2     Completed   0          16h   10.244.1.12   kube-node1               
rook-ceph-osd-prepare-kube-node2-5tbt6    0/2     Completed   0          16h   10.244.2.17   kube-node2               
rook-ceph-osd-prepare-kube-node3-rrp4z    0/2     Completed   0          16h   10.244.3.5    kube-node3               
rook-ceph-tools-76c7d559b6-7kprh          1/1     Running     0          16h   10.0.0.84     kube-node3               

And of course we can validate the Ceph cluster is up and healthy via the toolbox container as well:

# kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash

# ceph status
  cluster:
    id:     4be6e204-3d82-4cc4-9ea4-57f0e71f99c5
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum d,a,c
    mgr: a(active)
    osd: 4 osds: 4 up, 4 in
 
  data:
    pools:   0 pools, 0 pgs
    objects: 0  objects, 0 B
    usage:   17 GiB used, 123 GiB / 140 GiB avail
    pgs:     
 
# ceph osd tree
ID CLASS WEIGHT  TYPE NAME            STATUS REWEIGHT PRI-AFF
-1       0.13715 root default                                 
-4       0.03429     host kube-master                         
 2   hdd 0.03429         osd.2            up  1.00000 1.00000
-3       0.03429     host kube-node1                          
 1   hdd 0.03429         osd.1            up  1.00000 1.00000
-2       0.03429     host kube-node2                          
 0   hdd 0.03429         osd.0            up  1.00000 1.00000
-9       0.03429     host kube-node3                          
 3   hdd 0.03429         osd.3            up  1.00000 1.00000

Everything we have done up to this point has been very similar to what I did in the previous article with Minikube except instead of a single node we have a multiple node configuration. Now lets take it a step further and get an application to use our Ceph storage cluster.

The first step in Kubernetes will be to created a storageclass.yaml that uses Ceph.  Populate the storageclass.yaml with the following:

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: ceph.rook.io/block
parameters:
  blockPool: replicapool
  # The value of "clusterNamespace" MUST be the same as the one in which your rook cluster exist
  clusterNamespace: rook-ceph
  # Specify the filesystem type of the volume. If not specified, it will use `ext4`.
  fstype: xfs
# Optional, default reclaimPolicy is "Delete". Other options are: "Retain", "Recycle" as documented in https://kubernetes.io/docs/concepts/storage/storage-classes/

Next lets create the storage class using the yaml we created and set it to default:

# kubectl create -f storageclass.yaml
cephblockpool.ceph.rook.io/replicapool created
storageclass.storage.k8s.io/rook-ceph-block created

# kubectl get storageclass
NAME              PROVISIONER          AGE
rook-ceph-block   ceph.rook.io/block   61s

# kubectl patch storageclass rook-ceph-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/rook-ceph-block patched

# kubectl get storageclass
NAME                        PROVISIONER          AGE
rook-ceph-block (default)   ceph.rook.io/block   3m30s

Now that we have a storageclass that uses Ceph as the backend we now need an application to consume the storageclass. Thankfully the Rook git repo includes a couple of examples: Wordpress and MySQL. Lets go ahead and create those apps doing the following:

# cd ./rook/cluster/examples/kubernetes

# kubectl create -f mysql.yaml
service/wordpress-mysql created
persistentvolumeclaim/mysql-pv-claim created
deployment.apps/wordpress-mysql created

# kubectl create -f wordpress.yaml
service/wordpress created
persistentvolumeclaim/wp-pv-claim created
deployment.extensions/wordpress created



We can confirm our two applications are running by the following:

# kubectl get pods -n default -o wide
NAME                               READY   STATUS    RESTARTS   AGE     IP            NODE         NOMINATED NODE   READINESS GATES
wordpress-7b6c4c79bb-7b4dq         1/1     Running   0          68s     10.244.1.14   kube-node1              
wordpress-mysql-6887bf844f-2m4h4   1/1     Running   0          2m47s   10.244.1.13   kube-node1              

Now lets confirm if they are actually using our Ceph storageclass:

# kubectl get pvc

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-pv-claim   Bound    pvc-0c0be0ec-2317-11e9-a462-5254003ede95   20Gi       RWO            rook-ceph-block   3m36s
wp-pv-claim      Bound    pvc-46b4b266-2317-11e9-a462-5254003ede95   20Gi       RWO            rook-ceph-block   118s

And lets also confirm Wordpress is up and running from a user perspective. Note in this example we do not have an external IP and can only access the service via the cluster IP:

# kubectl get svc wordpress
NAME        TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
wordpress   LoadBalancer   10.104.120.47        80:32592/TCP 
  19m
# curl -v http://10.104.120.47
* About to connect() to 10.104.120.47 port 80 (#0)
*   Trying 10.104.120.47...
* Connected to 10.104.120.47 (10.104.120.47) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.104.120.47
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Mon, 28 Jan 2019 16:30:16 GMT
< Server: Apache/2.4.10 (Debian)
< X-Powered-By: PHP/5.6.28
< Expires: Wed, 11 Jan 1984 05:00:00 GMT
< Cache-Control: no-cache, must-revalidate, max-age=0
< Location: http://10.104.120.47/wp-admin/install.php
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host 10.104.120.47 left intact

We can see from the above output we do connect but get a 302 code since Wordpress really needs to be configured first. But it does confirm our applications are up and using the Ceph storageclass.

To clean up the previous exercise lets do the following:

# kubectl delete -f wordpress.yaml
service "wordpress" deleted
persistentvolumeclaim "wp-pv-claim" deleted
deployment.extensions "wordpress" deleted

# kubectl delete -f mysql.yaml
service "wordpress-mysql" deleted
persistentvolumeclaim "mysql-pv-claim" deleted
deployment.apps "wordpress-mysql" deleted

# kubectl delete -n rook-ceph cephblockpools.ceph.rook.io replicapool
cephblockpool.ceph.rook.io "replicapool" deleted

# kubectl delete storageclass rook-ceph-block
storageclass.storage.k8s.io "rook-ceph-block" deleted

The above example was just a simple demonstration of the capabilities Rook/Ceph bring to Kubernetes from a block storage perspective. But leaves one wondering what other possibilities there might be.

Further Reading:

Rook: https://github.com/rook/rook
Kubernetes: https://kubernetes.io/
Previous Article:  https://www.linkedin.com/pulse/deploying-ceph-rook-benjamin-schmaus/

Monday, May 25, 2015

UCS Vmedia Policy with XML & Perl


In Cisco UCS there is a concept called a VMedia policy.  This policy allows you to designate a bootable ISO image from another server via HTTP protocol and then have it available as a boot device for a Cisco UCS blade in UCSM.   The following script is a rough framework in Perl that uses XML calls to configure such a policy.    This script could be elaborated on to take inputs for some of the various variables that I pre-populate as an example of how the framework would work.


#!/usr/bin/perl
use strict;
use LWP::UserAgent;
use HTTP::Request::Common;
my $ucs = "https:///nuova";       # This is the URL to your UCSM IP
my $username = "admin";           # Admin or other user to manager UCSM
my $password = "password";        # Password for user above
my $server = "ls-servername0001"; # Server name as defined in UCS convention
my $server2 = "servername0001";   # Server name from friendly view
my $policyname = "$server2";      # Policy name (derived from server name in this example)
my $mntename = "$server2";        # Mount name (derived from server name in this example)
my $type = "cdd";                 # Policy mount type (in this case CD-ROM)
my $image = "$server2.iso";       # Name of ISO image
my $imagepath = "/";              # Image path within the URL of remote host serving ISO
my $mountproto = "http";          # Protocol used to access ISO image
my $remotehost = "";              # Remote host IP serving ISO image
my $serverdn = "org-root/org-corp/$server";     # Server DN within UCSM  

###  Everything below remains constant###
### Get Cookie ###

my ($xmlout,@xmlout,$cookie);
my $login="";
my $userAgent = LWP::UserAgent->new;
my $response = $userAgent->request(POST $ucs, Content_Type => 'text/xml', Content => $login);

(@xmlout)= split(/\s+/,$response->content);

### Process Cookie ###

foreach $xmlout (@xmlout) {
        if ($xmlout =~ /outCookie/) {
                $cookie=$xmlout;
                $cookie =~ s/outCookie=\"|\"//g;
                print "$cookie\n";
        }
}

###Setup Vmedia Policy String###

my $crpolicy = "";

###Configure Mount Policy Within Vmedia Policy String###

my $mountentry = " ";

###Add Vmedia Policy String###

my $addvmedia = "  ";

###Execute Setup Vmedia Policy String###

$response = $userAgent->request(POST $ucs, Content_Type => 'text/xml', Content => $crpolicy);
(@xmlout)= split(/\s+/,$response->content);
printxml();
print "\n";

###Execute Configure Mount Policy Within Vmedia Policy String###

$response = $userAgent->request(POST $ucs, Content_Type => 'text/xml', Content => $mountentry);
(@xmlout)= split(/\s+/,$response->content);
printxml();
print "\n";

###Execute Add Vmedia Policy String###

$response = $userAgent->request(POST $ucs, Content_Type => 'text/xml', Content => $addvmedia);
(@xmlout)= split(/\s+/,$response->content);
printxml();
print "\n";
exit;

###Parse XML response###

sub printxml {
        foreach $xmlout (@xmlout) {
                if ($xmlout =~ /=/) {
                        $xmlout =~ s/\'|\"|\/|\>//g;
                        $xmlout =~ s/=/ = /g;
                        print "$xmlout\n";
                }
        }
}

Sunday, May 24, 2015

Deleting Duplicate Hosts In Satellite or Spacewalk


Sometimes when you register hosts to Satellite or Spacewalk you end up with duplicate hosts being registered.   The old one that will no longer check in and just be an orphan and a new one which will check in and get updates and packages.   I saw this behavior a lot in environments where people were using Vagrant and/or Openstack where they would continuously launch the same host with the same hostname and register it to Satellite. 

The script  below can be used to clean out those duplicate hosts and can be setup to run from cron daily.   The script assumes you run this as the root user and have configured root to run spacecmd without specifying username and password at the command line.  This was tested with Satellite 5.6.

#!/usr/bin/perl
### Delete duplicate hosts in Satellite or Spacewalk via Spacecmd ###
@duphosts = `spacecmd -q system_list | uniq -d`;
foreach $system (@duphosts) {
        chomp($system);
        $spacecmd = `spacecmd system_details $system 2>&1 |grep $system |grep =|sed 's/^.*=/=/'`;
        $spacecmd =~ s/\s+//g;
        $spacecmd =~ s/=//g;
        @ids = split(/\,/,$spacecmd);
        $count=0;
        foreach $ids (@ids) {
                chomp($ids);
                $count++;
                if ($count > $#ids) {
                        print "Duplicates removed for system: $system\n";
                        last;
                }
                $cmd = `spacecmd -y system_delete $ids`;
                print "$cmd\n";
                sleep (2);
        }
        print "Cleanup of $system complete...\n";
}
print "Cleanup of Satellite complete!\n";

Syncing Redhat Repos With Pulp


The following is a basic installation/configuration guide to setting up Pulp to pull package channels from Redhat's CDN(Content Delivery Network) so that you can leverage Spacewalk, Suse Manager or another repository manager that would not normally be able to access Redhat directly.

Assumptions:  This assumes you are installing Pulp on Redhat 6.6, although I don;t see why this would not work on Redhat 7.0 or another Linux distro for that matter.  The only changes would be the switch from init scripts to systemd in the below documentation.

1)  Register the host with Redhat directly to receive its updates:
      #subscription-manager register --force
      #subscription-manager refresh
      #subscription-manager subscribe --auto

2) Run an update to confirm you can access the Redhat repos properly:
     #yum upgrade

3) Install the Pulp repo and the Linux Epel repo as you will need packages from both:
    #rpm -Uvh https://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
    #rpm -Uvh http://repos.fedorapeople.org/repos/pulp/pulp/rhel-pulp.repo

4) Install, enable and install mongodb server:
    #yum install mongodb-server
    #service mongod start
    #chkconfig mongod on

5) Install, enable and start qpidd:
    #yum install qpid-cpp-server qpid-cpp-server-store
    #service qpidd start
    #service qpidd on
    #chkconfig qpidd on

6) Install Pulp group of packages:
    #yum groupinstall pulp-server-qpid

7) Run Pulp database setup to populate Pulp database:
    #sudo -u apache pulp-manage-db

8) Enable and start web service:
    #service httpd start
    #chkconfig httpd on
    #systemctl enable httpd

9) Enable and start pulp workers, celery beat and pulp resource manager:
    #chkconfig pulp_workers on
    #service pulp_workers start
    #chkconfig pulp_celerybeat on
    #service pulp_celerybeat start
    #chkconfig pulp_resource_manager on 
    #service pulp_resource_manager start

10) Install pulp-admin packages:
      #yum groupinstall pulp-admin

11) Install Pulp consumer qpid package:
      #yum groupinstall pulp-consumer-qpid

12) Edit Pulp admin.conf, consumer.conf and agent.conf to your specifications:
      #vi /etc/pulp/admin/admin.conf
      #vi /etc/pulp/consumer/consumer.conf
      #vi /etc/pulp/agent/agent.conf

At this point Pulp should be ready to consume something from the Redhat Content Delivery Network.   Lets see what setting up a sync looks like in the following steps.

1) Create a repo with the feed location and the correct certs and keys related to accessing that repo feed.
    #pulp-admin rpm repo create --repo-id=rhel-6-server-rpms --feed=https://cdn.redhat.com/content     /dist/rhel/server/6/6Server/x86_64/os --feed-ca-cert=/etc/rhsm/ca/redhat-uep.pem --feed-key=/etc/pki/entitlement/5161085288703435774-key.pem --feed-cert=/etc/pki/entitlement/5161085288703435774.pem

2) (Optional) Configure the repo you created with the number of download workers and max download speed.   This helps if you are pulling packages down over a smaller WAN link and do not want to saturate it.
    #pulp-admin rpm repo update --max-speed=14000 --repo-id=rhel-6-server-rpms
    #pulp-admin rpm repo update --max-downloads=2 --repo-id=rhel-6-server-rpms

3) Configure the repo so that it is served up via the web server so it can be consumed via HTTP:
    #pulp-admin rpm repo update --repo-id=rhel-6-server-rpms  --serve-http=true

4) Sync the repo from the source, in this case in our example Redhat:
    #pulp-admin rpm repo sync run --repo-id=rhel-6-server-rpms

5) (Optional) Setup the sync in step 4 in a cron job to occasionally sync the newer packages to keep your Pulp repo up to date.







Friday, January 02, 2015

Verifying Firmware (OBP) on Oracle (Sun Microsystems) Hardware


To verify the firmware of a Sun Microsystems server use one of the following options:

At the OBP, use the '.version' command:

ok .version
Firmware CORE Release 1.0.18 created 2002/5/23 18:22
Release 4.0 Version 18 created 2002/05/23 18:22
cPOST version 1.0.18 created 2002/5/23
CORE 1.0.18 2002/05/23 18:22
ok

When running Solaris, use the prtconf(1M) command:

# prtconf -V
OBP 4.0.18 2002/05/23 18:22
#

Powersearch Old Files with Bash Waste Script


Waste is a simple script that makes it easier to search for old files in a path on a linux systems.   Waste features allow you do display the listing sorted by day or size and search for files a specific number of days old or between a range of days old.    It simplifies the usage of the find command by wrapping it up into a easy to use script.   Its appeal is as a cleanup script for directories that have files that need to be purged over time.

#!/bin/bash
if [[ $# = 0 || $1 = "-h" || "$#" -lt 4 ]]; then
        echo "Usage: [-d|-s] [-r {start end}|-o {start}] directory"
        echo " -h displays the help"
        echo " -d display listing sorted by day"
        echo " -s display listing sorted by size"
        echo " -r display files between a specific number of days old"
        echo " -o display files older then a specific number of days"
        echo " Example1: waste -d -r 10 20 /home"
        echo " Example2: waste -s -o 30 /process"
        exit
fi
case "$1" in
        '-s')
                case "$2" in
                        '-r')
                                if [ "$#" -lt 5 ]; then
                                        echo "Usage: [-d|-s] [-r {start end}|-o {start}] directory"
                                        exit
                                fi
                                printf "Date\t\tSize\tDirectory/File\n"
                                /usr/bin/find $5 -type d -daystart -mtime +$3 -mtime -$4 -printf "%CY-%Cm-%Cd\t" -maxdepth 1 -exec /usr/bin/du.new -s --block-size=M "{}" \; | /bin/sort -k2nr
                                ;;
                        '-o')
                                if [ "$#" -lt 4 ]; then
                                        echo "Usage: [-d|-s] [-r {start end}|-o {start}] directory"
                                        exit
                                fi
                                printf "Date\t\tSize\tDirectory/File\n"
                                /usr/bin/find $4 -type d -daystart -mtime +$3 -printf "%CY-%Cm-%Cd\t" -maxdepth 1 -exec /usr/bin/du.new -s --block-size=M "{}" \; | /bin/sort -k2nr
                                ;;
                esac
        ;;
        '-d')
                case "$2" in
                        '-r')
                                if [ "$#" -lt 5 ]; then
                                        echo "Usage: [-d|-s] [-r {start end}|-o {start}] directory"
                                        exit
                                fi
                                printf "Date\t\tSize\tDirectory/File\n"
                                /usr/bin/find $5 -type d -daystart -mtime +$3 -mtime -$4 -printf "%CY-%Cm-%Cd\t" -maxdepth 1 -exec /usr/bin/du.new -s --block-size=M "{}" \; | /bin/sort -k2nr | /bin/sort
                                ;;
                        '-o')
                                if [ "$#" -lt 4 ]; then
                                        echo "Usage: [-d|-s] [-r {start end}|-o {start}] directory"
                                        exit
                                fi
                                printf "Date\t\tSize\tDirectory/File\n"
                                /usr/bin/find $4 -type d -daystart -mtime +$3 -printf "%CY-%Cm-%Cd\t" -maxdepth 1 -exec /usr/bin/du.new -s --block-size=M "{}" \; | /bin/sort -k2nr | /bin/sort
                                ;;
                esac
        ;;
esac 

Wednesday, December 31, 2014

x86 Hardware RAID Traps with BS_RAID_CHK


The following script is designed to run on Solaris x86 or Redhat systems with LSI and Adaptec hardware controllers.  It will check for degraded states of those controllers using mpt-status, arccon or raidctl depending on hardware vendor and OS.   If a degraded state is found it will send an snmptrap to your snmptrap collector.

#!/usr/bin/perl
#################################################################
# Script checks status of hardware raid on X86 hardware        #
# Supported OS's: Solaris x86, Redhat                #
# Supported Controller's: LSI & Adaptec                #
# Note: Requires mpt-status for LSI                #
# Note: Requires arccon for adaptec                #
# Note: Requires raidctl for Solaris x86            #
# Sends trap if degraded state                    #
#################################################################
use strict;
my $prefix = "bs_raid_chk";
my $servicechk = "unix_traps";
my $community = "asdpublic";
my $manager = "10.66.65.23";
my $raidctl = '/usr/sbin/raidctl';
my $mptstatus = '/usr/sbin/mpt-status';
my $arcconf = '/usr/StorMan/arcconf';
my (@components,@command);
my ($num,$status,$sendtrap,$volume);
### If Solaris System use this check ###
if (`uname -a` =~ /SunOS/) {
    if ( -e $raidctl) {
        @command = `$raidctl -S`;
        foreach (@command) {
            chomp();
            @components = split();
            $num = $components[1] + 2;
            $status = "$components[$#components] - Controller: $components[0], RAID: $components[$num], Number of Disks: $components[1]\n";
            if ($_ =~ /DEGRADED/) {
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.CRITICAL";
                system "/usr/sfw/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1005 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                        exit;
            } elsif ($_ =~ /SYNC/) {
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                                system "/bin/touch /tmp/$prefix.WARNING";
                system "/usr/sfw/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1006 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                        exit;
            } elsif ($_ =~ /OPTIMAL/) {
                if ( !-e "/tmp/$prefix.OK" ) {
                    system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                    system "/bin/touch /tmp/$prefix.OK";
                    system "/usr/sfw/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1007 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                }
            }
        }
    }
}
### If Linux system use this check  ###
if (`uname -a` =~ /Linux/) {
    # If system has LSI controller, then mptstatus should be installed
    if (-e $mptstatus) {
        my $modstatus = `/sbin/lsmod |grep mptctl|wc -l`;
        chomp($modstatus);
        if ($modstatus eq "0") {
            my $modload = `/sbin/modprobe mptctl`;
            $modstatus = `/sbin/lsmod |grep mptctl|wc -l`;
            chomp($modstatus);
            if ($modstatus eq "0") { print "ABORT: Failed to load mptctl module.\n";exit;}
        }
        my $controller = `$mptstatus -p -s|grep Found`;
        chomp($controller);
        my ($id,$junk) = split(/,/,$controller);
        $id =~ s/Found SCSI id=//g;
        @command = `$mptstatus -i $id -s`;
        $status="";
        foreach (@command) {
            chomp();
            $status = "$status $_";   
        }
        $status = "$status";
        #print "$status\n";
        foreach (@command) {
            chomp();
            if ( $_ =~ /DEGRADED/ ) {
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.CRITICAL";   
                system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1005 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                exit;
            } elsif ($_ =~ /SYNC/ ) {
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.WARNING";
                system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1006 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                exit;
            } elsif ($_ =~ /OPTIMAL/ ) {
                if ( !-e "/tmp/$prefix.OK" ) {
                    system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                    system "/bin/touch /tmp/$prefix.OK";
                    system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1007 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                }
            }
        }
    }
    # if system has Adaptec controller then arcconf should be installed
    if ( -e $arcconf ) {
        @command = `$arcconf getconfig 1|grep Status|grep :`;
        foreach (@command) {
            if (( $_ =~ /Controller Status/ ) && ($_ !~ /Optimal/ )) {
                $status = "Controller not optimal";
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.CRITICAL";
                system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1005 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                exit;
            }
            if (( $_ =~ /  Status  / ) && ($_ !~ /Optimal/ )) {
                $status = "Battery not optimal";
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.WARNING";
                system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1006 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                exit;
            }
            if (( $_ =~ /Status of logical device/) && ($_ !~ /Optimal/ )) {
                $status = "Logical HW RAID Volume not optimal";
                system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                system "/bin/touch /tmp/$prefix.CRITICAL";
                system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1005 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                exit;
            }
            if ( $_ =~ /Optimal/ ) {
                if ( !-e "/tmp/$prefix.OK" ) {
                    $status = "Hardware RAID - OK";
                    system "/bin/rm /tmp/$prefix.* >/dev/null 2>&1";
                    system "/bin/touch /tmp/$prefix.OK";
                    system "/usr/bin/snmptrap -v 2c -c $community $manager '' .1.3.6.1.4.1.11.2.17.1.0.1007 .1.3.6.1.4.1.11.2.17 s \"$servicechk\" .1.3.6.1.4.1.11.2.17 s \"$status\"";
                }
            }
        }
    }   
}
exit; 


Perl Script to Generate Logon.bat for SAMBA Users


The following script will generate a vanilla logon.bat file for SAMBA users. 

#!/usr/bin/perl
################################
# Usage: smb-logon-script      #
################################

$startpath="/data/smb-logon-scripts";
$endpath="/data/netlogon/scripts";
$smbhost = "sambahost.domain.com";


@alpha = ("g"..."t","v"..."z","aa"..."zz");
if (! defined $ARGV[0] ) {
        print " Usage: smb-logon-script \n";
        exit;
}
$username = $ARGV[0];
@group = `/usr/bin/getent group|/bin/grep $username |/bin/cut -d: -f1 -`;
$counter=0;
open FILE, ">$startpath/$username.bat.unix";
foreach $group (@group) {
        print FILE "net use $alpha[$counter]: \\\\$smbhost\\$group";
        $counter++;
}
close (FILE);
$convert = `/usr/bin/dos2unix < $startpath/$username.bat.unix > $endpath/$username.bat`;
exit;

OpenStack Neutron Distributed Virtual Routing Architectural Overview (Icehouse vs Juno)

Layer 3 Routing in Icehouse:


Layer 3 Routing in Juno with DVR:


Sun Microsystems SunFire V100

Here is a trip down memory lane today with the old SunFire V100.    


And under the cover:




Ceph Architectural Overview


Monday, December 29, 2014

Persistantly Bind Tape Devices in Solaris via Perl


The following script will look for fiber channel tape devices and then configure the devlinks.tab file with the appropriate information so the tape drives will persistently bind to the same device across reboots on a Solaris server.   This script was tested on Solaris 10.
#!/usr/bin/perl
use strict;
my($junk,$path,$devices,$dev,$file);
my(@devices,@file);
my $date = `date +%m%d%Y`;
$file = `/usr/bin/cp /etc/devlink.tab /etc/devlink.tab.$date`;
@file = `cat /etc/devlink.tab`;
@file = grep !/type=ddi_byte:tape/, @file;
open (FILE,">/etc/devlink.tab.new");
print FILE @file;
close (FILE);
 
@devices = `ls -l /dev/rmt/*cbn|awk {'print \$9 \$11'}`;
open (FILE,">>/etc/devlink.tab.new");
foreach $devices (@devices) {
        chomp($devices);
        ($dev,$path) = split(/\.\.\/\.\./,$devices);
        $dev =~ s/cbn//g;
        $dev =~ s/\/dev\/rmt\///g;
        $path =~ s/:cbn//g;
        ($junk,$path) = split(/st\@/,$path);
        print FILE "type=ddi_byte:tape;addr=$path;\trmt/$dev\\M0\n";
}
close (FILE);
$file = `/usr/bin/mv /etc/devlink.tab.new /etc/devlink.tab`;
exit;

Ceph Repair One Liner


The following one liner will look for inconsistent page groups in a Ceph page group dump and repair them.    Nice quick way to fix-up inconsistencies!
ceph pg dump | grep -i incons | cut -f 1 | while read i; do ceph pg repair ${i} ; done

Solaris LUN Online Report


If you are using fiber channel storage with Solaris in a multipath configuration, sometimes before fabric maintenance or array maintenance you might want to check and confirm the status of all the paths on the Solaris client.   The following script utilizing luxadm will report on the status of each path for a fiber channel device.

#!/bin/perl @luns = `/usr/sbin/luxadm probe | grep Logical | sed -e 's/.*\://g'|grep rdsk`; foreach $lun (@luns) { chomp($lun); $lun2 = $lun; $lun2 =~ s/\/dev\/rdsk\///g; print "Disk:$lun2\t"; @luxadm = `/usr/sbin/luxadm display $lun`; $pathcount = 0; foreach $luxadm (@luxadm) { chomp($luxadm); if ($luxadm =~ /State/) { $luxadm =~ s/State//g; $luxadm =~ s/^\s+//; print "Path$pathcount:$luxadm\t"; $pathcount++; } } print "\n"; }
The output from the script will look something like the output below:
#perl pathfinder.pl Disk:c6t60060E80054337000000433700000526d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000527d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000301d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000300d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000278d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000277d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE Disk:c6t60060E80054337000000433700000275d0s2 Path0:ONLINE Path1:ONLINE Path2:ONLINE Path3:ONLINE

Cleanup Shared Memory Segments Solaris


If you have ever used an application in Solaris that uses shared memory and that application has a tendency to not cleanup those memory segments properly on shutdown (SAP comes to mind)  then this little Perl script is what you have been waiting for.

All this script does is take certain field output from the ipcs command and then iterate through it to determine if the memory is still actively in use by a process or if it it can safely be purged.   I recommended testing this out with the $memclean line commented out to gain a good understanding before you remove the comment and allow the cleanup (#$memclean = `ipcrm -m $shmem`;).  This script was tested on Solaris 10.
#!/usr/bin/perl @sms = `ipcs -pm|grep "^m"|awk {'print \$2":"\$7":"\$8'}`; foreach $sms (@sms) { chomp($sms); ($shmem,$cpid,$lpid) = split(/:/,$sms); $cpids=` ps -ef|grep $cpid|grep -v grep >/dev/null 2>&1;echo \$?`; $lpids=` ps -ef|grep $lpid|grep -v grep >/dev/null 2>&1;echo \$?`; chomp($cpids,$lpids); if (($cpids eq "1") && ($lpids eq "1")) { $message = "Memory can be reclaimed"; #$memclean = `ipcrm -m $shmem`; } else { $message = "Memory active"; } print "$shmem - $cpid - $lpid - $cpids - $lpids - $message\n"; }
The output from the script will look similar to the following:
# perl mem_clean.pl 587203562 - 10885 - 17891 - 0 - 0 - Memory active 922746991 - 9728 - 10885 - 0 - 0 - Memory active 150995435 - 9728 - 10885 - 0 - 0 - Memory active 150995432 - 9728 - 17891 - 0 - 0 - Memory active 150995398 - 17421 - 17891 - 1 - 0 - Memory active 150995396 - 13421 - 13891 - 1 - 1 - Memory can be reclaimed 150995387 - 9728 - 10885 - 0 - 0 - Memory active 150995382 - 9728 - 10885 - 0 - 0 - Memory active 150995380 - 9728 - 10885 - 0 - 0 - Memory active 150995379 - 9728 - 10885 - 0 - 0 - Memory active 150995377 - 9728 - 17891 - 0 - 0 - Memory active 150995374 - 9728 - 17891 - 0 - 0 - Memory active 150995371 - 9727 - 10886 - 1 - 0 - Memory active 117440821 - 9728 - 10885 - 0 - 0 - Memory active 117440819 - 9728 - 10885 - 0 - 0 - Memory active

Saturday, December 20, 2014

Cleaning Up OpenStack Instances in Redhat Satellite or Spacewalk


When using OpenStack with instances that I wanted to have registered with Redhat Satellite or Spacewalk, I was left wondering what would happen to all those registered hosts once they were terminated in OpenStack?

If I chose to do nothing, the answer was I would be left of orphaned hosts in Redhat Satellite or Spacewalk and over time this could lead to higher license costs if leveraging support for Redhat Linux or just pure database bloat due to having all these previously used instances still referenced in my database.

This issue bothered me and I wanted a mechanism that would cleanup instances one they were terminated but the question was how to go about it?

Well I soon realized that OpenStack keeps a record of all the instances it ever created and or terminated.  It was the terminated part that would be a key component to what I wanted to accomplish.  I figured if I could mine out the deleted data of instances, I could cross check those against Redhat Satellite or Spacewalk.

The Perl script below does just that.   I have it run every 24 hrs out of cron and it first goes into the OpenStack nova database and scrapes the instances table for any instances that were marked deleted in the last 24 hours.   Any instances it finds it puts into an array that I then enumerate through using the spacecmd tools and check within Satellite or Spacewalk to see if the host is registered.  If the host is registered, I then remove the host given that it is no longer a valid host that is up and running.

#!/usr/bin/perl $cmd=`rm -r -f /root/.spacecmd/spacewalk.schmaustech.com`; $yestdate=`TZ=CDT+24 /bin/date +%y-%m-%d`; #$yestdate=`TZ=CDT /bin/date +%Y-%m-%d`; chomp($yestdate); @delhosts=`mysql -e "select hostname,uuid,deleted_at from nova.instances where deleted_at is not null"|grep $yestdate`; foreach $delhost (@delhosts) { ($hostname,$uuid) = split(/\s+/,$delhost); $uuid2 = $uuid; $uuid2 =~ s/-//g; @cmdout=`spacecmd -q system_details $hostname.schmaustech.com`; foreach $cmd (@cmdout) { chomp($cmd); if ($cmd =~ /$uuid2/) { $message = "Removing from satellite hostname: $hostname with UUID: $uuid...\n"; $cmdtmp = `logger $message`; $cmdtmp = `spacecmd -y -q system_delete $hostname.schmaustech.com`; } } } exit;

Configuring DVR in OpenStack Juno

Before Juno, when we deploy Openstack in production, there was always a painful point about the single l3-agent node which caused two issues: a performance bottleneck and a single point of failure (albeit there were some non-standard ways around this issue).   Now Juno comes with new Neutron features to provide HA L3-agent and Distributed Virtual Router (DVR).

DVR distributes East-West traffic via virtual routers running on compute nodes. Also virtual routers on compute nodes handle North-South floating IP traffic locally for VM running on the same node. However if floating IP is not in use, VM originated external SNAT traffic is still handled centrally by virtual router in controller/network node.  These aspects spread the load of network traffic across your compute nodes and your network controller nodes thus distributing network performance.

HA L3 Agent provides virtual router HA by VRRP. A virtual gateway IP is always available from one of controller/network nodes thus eliminating the single point of failure.

The following blog will discuss how to configure DVR in Juno in a complete configuration aspect.   In this example we used RHEL7 on Redhat’s RDO for Juno.

The host configuration is 3 nodes, one management node, and two compute nodes.   Each node has a data interface for access to the node itself and a bridge interface for the floating-ip network that allows instances access outside of their private subnet to the physical network.

I ran through a standard packstack install specifying GRE tunnels for my connectivity between my management and compute nodes.  Be aware that the current version of DVR only supports GRE or VXLAN tunnels as VLANS are not yet supported.    I then configured the setup as if I was using standard neutron networking for a multi-tenant setup, that is all my instances would route traffic through the l3-agent running on the management node (similar behavior in Icehouse and Havana).  Once I confirmed this legacy setup was working then moved on to changing it to use DVR on the compute nodes.


On the management node where the neutron server runs edit the following files: neutron.conf, l3_agent.ini, ml2_conf.ini and ovs_neutron_plugin.ini

In /etc/neutron/neutron.conf

Edit the lines to state the following by either adding or uncommenting them:

router_distributed = True
dvr_base_mac = fa:16:3f:00:00:00

Note:  When creating a network as admin, one can override the distributed router by using the following flag:  "--distributed False"

In /etc/neutron/l3_agent.ini

Edit the line to state the following:

agent_mode = dvr_snat

Note:  This will provide the SNAT translation for any instances that do not get assigned a floating-ip.  Therefore they will route through the central l3-agent on the management node if they need outside access but will not have a floating-ip associated.  Given the l3-agent at the management node can be HA in Juno, this will still not ne a single point of failure.  However we are not covering that topic in this article.

In /etc/neutron/plugins/ml2/ml2_conf.ini

Edit the line to state the following:

mechanism_drivers = openvswitch, l2population

In /etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini

Edit or add the lines to state the following:

l2_population = True
enable_distributed_routing = True

One each of the compute nodes do the following steps:

Make the ml2 plugin directory. copy over the ml2_conf.ini from neutron node and setup softlink:

mkdir /etc/neutron/plugins/ml2
rsync -av root@ctl1:/etc/neutron/plugins/ml2 /etc/neutron/plugins
cd /etc/neutron
ln -s plugins/ml2/ml2_conf.ini plugin.ini

Copy over the metadata_agent.ini from the neutron server node:

rsync -av root@ctl1:/etc/neutron/metadata_agent.ini /etc/neutron

In /etc/neutron/ l3_agent.ini

Edit the line to state the following:

agent_mode = dvr

In /etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini

Edit or add the lines to state the following:

l2_population = True

enable_distributed_routing = True

One final step on the compute node is to associate the br-ex interface with the physical interface on the compute node that will bridge the floating-ip’s to the physical vlan.

ovs-vsctl add-port br-ex

Restart the openstack services on the management node.

Restart the openstack services on the compute node as well.  Also ensure you start the l3-agent and metadata service on the compute node.

If you plan on using Horizon to spin up instances and associate floating-ip’s, you will need to make the following edit in the Horizon code as there is a bug:  https://bugs.launchpad.net/horizon/+bug/1388305.   Without the code update, you will not see a list of valid ports to associate the floating-ip to on the instance.  This association does work from the cli however without modification.

Edit the following file:  /usr/share/openstack-dashboard/openstack_dashboard/api/neutron.py

Find the line:

p.device_owner == 'network:router_interface'

And replace it with:

p.device_owner == 'network:router_interface'   or p.device_owner == 'network:router_interface_distributed'

Restart the httpd service.

Once you have followed the steps above you should be able to spin up an instance and associate a floating-ip to it and that instance will be accessible via the compute node l3-agent.   You can confirm a proper namespace is setup by running the following on the compute node:

ip netns

fip-4a7697ba-c29c-4a19-9b92-2a9194e1d6de
qrouter-6b4a2758-3aa7-4603-9fcd-f86f05d0c62

The fip is the floating-ip namespace and the qrouter is just like the namespaces previously seen on a network management node.  You can use ip netns exec commands to explore those namespaces and further troubleshoot should the configuration not be working.

Another way to confirm traffic is coming to your instance directly on the compute node is to use tcpdump and sniff on the physical network interface that is bridging to the physical network for the floating-ip network.  Then while running tcpdump you can ping your instance from another host somewhere on your network and you will see the packets in the tcpdump.

DVR promising to provide a convenient way of distributing network traffic loads to the compute nodes of the instances on them and helps to alleviate the bottleneck of the neutron management node.

Thursday, November 27, 2014

Lookup Tenant of Floating IP Address in OpenStack


Let's say your security team is doing routine scanning and they find that a few of your OpenStack instances running in your cloud are not passing the security test, what do you do?

You whip up a quick and dirty bash script that takes the floating ip address as an argument and then provides the name of the tenant that ip address belongs to:

#!/bin/bash
FLOAT=`neutron floatingip-list |grep $1|awk -F '|' {'print $2'}`
TENANT=`neutron floatingip-show $FLOAT|grep tenant|awk -F '|' {'print $3'}`
keystone tenant-get $TENANT

Sample run:

 
 ./float2tenant.sh 10.63.10.193
+-------------+---------------------------------------------------------+
|   Property     |                          Value                                                    |
+-------------+---------------------------------------------------------+
| description | This is a sample project                                                     |
|   enabled   |                           True                                                        |
|      id         |             981690ddbe5347bda5c73415134d6664              |
|     name     |                     Project 1                                                       |
+-------------+---------------------------------------------------------+

Friday, May 16, 2014

Faking Out Ceph-Deploy in OpenStack


I wanted to build a functional Ceph deployment for testing but did not have hardware to use.   So I decided I would use my instances in OpenStack.   The image choice I used for this configuration was the stock RHEL 6.5 cloud image from Redhat.   However when I went to do a ceph-deploy install on my monitor server, I ran into this:
[root@ceph-mon ceph]# ceph-deploy install ceph-mon [ceph_deploy.conf][DEBUG ] found configuration file at: /root/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (1.5.2): /usr/bin/ceph-deploy install ceph-mon [ceph_deploy.install][DEBUG ] Installing stable version firefly on cluster ceph hosts ceph-mon [ceph_deploy.install][DEBUG ] Detecting platform for host ceph-mon ... [ceph-mon][DEBUG ] connected to host: ceph-mon [ceph-mon][DEBUG ] detect platform information from remote host [ceph_deploy][ERROR ] UnsupportedPlatform: Platform is not supported:
It didn't really say what platform it thought this was that was unsupported, but I knew that Redhat 6.5 was supported so it really did not make any sense.   What I discovered though was that the following file was missing within my cloud image:
/etc/redhat-release

So I manually add it:
$ vi /etc/redhat-release Red Hat Enterprise Linux Server release 6.5 (Santiago)

Then when I reran ceph-deploy it detected a supported platform:
[root@ceph-mon ceph]# ceph-deploy install ceph-mon [ceph_deploy.conf][DEBUG ] found configuration file at: /root/.cephdeploy.conf [ceph_deploy.cli][INFO ] Invoked (1.5.2): /usr/bin/ceph-deploy install ceph-mon [ceph_deploy.install][DEBUG ] Installing stable version firefly on cluster ceph hosts ceph-mon [ceph_deploy.install][DEBUG ] Detecting platform for host ceph-mon ... [ceph-mon][DEBUG ] connected to host: ceph-mon [ceph-mon][DEBUG ] detect platform information from remote host [ceph-mon][DEBUG ] detect machine type [ceph_deploy.install][INFO ] Distro info: Red Hat Enterprise Linux Server 6.5 Santiago [ceph-mon][INFO ] installing ceph on ceph-mon [ceph-mon][INFO ] Running command: yum clean all