sábado, 19 de octubre de 2019

Cluster ONTAP - Replicación de datos con SnapMirror - Parte II

Hoy continuamos estudiando las funcionalidades que nos ofrece SnapMirror y cómo configurarlo para proteger los datos de nuestros sistemas ONTAP.

En el anterior post establecimos una relación de protección entre dos clusters, para lo cual realizamos la configuración de la relación de peering entre ambos y la transferencia inicial o baseline del volumen que queremos proteger.

Recordemos que el funcionamiento de SnapMirror consiste en que el sistema destino, en función del schedule que establezcamos, creará un snapshot del volumen en el sistema origen y lo transferirá junto con otros snapshots que pudiesen existir, lo cual estará en función de la política de replicación que establezcamos para la relación de SnapMirror.

Por tanto, una de las configuraciones es la política de replicación la cual, de forma simple, establece que snapshots queremos transferir al sistema destino. Las políticas de replicación predefinidas en ONTAP son las siguientes:
  • MirrorAllSnapshots, esta política establece que en cada replicación deben transferirse todos aquellos snapshots que se hayan realizado desde la última replicación, así como el snapshot realizado por el propio SnapMirror.
  • MirrorLatest, esta política establece que solo se transferirá el snapshot creado por SnapMirror en cada replicación.
Si listamos las políticas predefinidas para el tipo async-mirror correspondiente a SnapMirror, veremos lo siguiente:

Políticas predefinidas para SnapMirror.
Como podemos ver hay tres políticas diferentes, siendo la política DPDefault la política correspondiente al modo de replicación Data Protection que, como recordaremos, era el modo por defecto empleado al crear una relación SnapMirror hasta ONTAP 9.3. Si comparamos esta polítca con la política MirrorAllSnapshots podemos comprobar que son idénticas, ya que esta es la nueva política por defecto empleada desde ONTAP 9.3.

Como podemos ver en la salida del comando anterior, las políticas contienen una serie de reglas con lo que vamos a analizarlas más detenidamente:

Política MirrorAllSnapshots.
Política MirrorLatest.
Como podemos ver, la principal diferencia entre ambas políticas es que MirrorAllSnapshots contiene una regla que indica que hay que transferir todos los snapshots del origen, indicada por la regla número 2 y la etiqueta all_source_snapshots. La pregunta es ¿que son estas reglas y cual es su relación con la transferencia de snapshots?

Para esto primero empecemos por analizar un snapshot en nuestro sistema origen:

Características de un snapshot.
De todas las características que podemos ver en la salida, vemos que hay un campo Label for SnapMirror Operations, cuyo valor es standard. Debemos recordar, que esta etiqueta de snapshots la fijamos nosotros en el momento de crear las políticas de snapshots para nuestros volúmenes.

Si buscamos este mismo snapshot en el sistema destino veremos que tiene las mismas características y que, salvo los valores que cambian debido a que se ha transferido al cluster destino, veremos que el campo de etiqueta contiene el mismo valor identificado como standard.

Adicionalmente, si nos fijamos en los snapshots creados por las operaciones de snapmirror, identificados con el nombre snapmirror tanto en origen como en destino, veremos que también tienen un campo label, pero en este caso está vacia.

Lo que establecen las reglas de las políticas de SnapMirror es el nombre de la etiqueta de los snapshots que se transferiran y, en el caso de las políticas predefinidas las reglas establecen:
  • sm_created - Esta etiqueta fuerza la transferencia del snapshot creado por SnapMirror en cada transferencia programada. Este snapshot siempre estará identificado con una etiqueta SnapMirror vacía. Como indica la documentación, esta regla no puede modificarse ni puede eliminarse de la política.
  • all_source_snapshots - Esta etiqueta indica que deben transferirse todos los snapshots del volumen origen, realizados desde la útlima replicación.

Veamos un ejemplo modificando la etiqueta de un snapshot del volumen origen. Para esto, primero vamos a crear un snapshot del volumen origen al cual vamos a fijar una etiqueta de snapmirror diferente, lo cual podemos hacer con un comando como el siguiente:

Creación de un snapshot con una etiqueta de pruebas.
Ahora podemos ver los snapshots del volumen y veremos que la etiqueta es difernte del resto de snpashots:

Lista de snapshots del volumen origen.
Con esta nueva etiqueta y la programación que establecimos para la relación de SnapMirror, vemos que la regla all_source_snapshots transfiere todos los snapshots existentes en el volumen origen como ya hemos visto independientemente de su etiqueta, y por tanto ahora tenemos nuestro snapshot snap_test en el volumen destino:

Snapshots del volumen destino incluyendo el snapshot de test.
Teniendo en cuenta que SnapMirror es una solución de disaster recovery y que, como es lógico, deberíamos tener nuestros datos de origen totalmente replicados en destino, el comportamiento de la política MirrorAllSnapshots es el esperado en ese caso.

Al comparar esta política con la política MirrorLatest, vemos que la diferencia fundamental es que únicamente contiene la regla con la etiqueta sm_created, es decir, todas aquellas relaciones de protección que utilicen esta política solo transferiran el snapshot creado por SnapMirror en cada actualización.

Y nos podemos preguntar ¿y si quiero crear mi propia política de SnapMirror y especificar reglas diferentes? Pues en ese caso, como podemos ver a continuación, veremos que no es posible cuando usamos una política de tipo async-mirror.

Emepcemos creando una nueva política de Snapmirror en nuestro cluster destino, en la cual vamos a especificar que es de tipo async-mirror por tratarse de una relación SnapMirror y que habrá una regla para los snapshots con una etiqueta DEVELOP, forzando así que no se transfieran todos los snapshots que puedan existir en el volumen origen, sino solo aquellos que contengan dicha etiqueta. Esta política podemos crearla con el siguiente comando:
Creación de una política de SnapMirror.
Como ya hemos visto, esta política recien creada tiene por defecto la regla sm_created, para asegurar que se transferirá en todas las actualziaciones de la relación de protección, el snapshot creado por SnapMirror:

Nuestra política recien creada. La regla sm_created la incluye ONTAP.
Si ahora intentamos añadir la regla indicando la etiqueta de los snapshots de origen que queremos que se transfieran, que en este caso es DEVELOP recibiremos un bonito mensaje de error como el siguiente:

Error al intentar modificar una política de tipo async-mirror.
De nuevo pensemos que SnapMirror es una solución de disaster recovery, está diseñado para replicar todos los datos de nuestros volúmenes de origen y establecer reglas para determinadas etiquetas implicaría no replicar toda la información. Para poder establecer relaciones de protección, en las que controlar los snapshots de origen que deben transferirse a destino, tendremos que establecer relaciones de SnapVault lo cual veremos en futuras entradas.

Por tanto, y en resumen, las políticas de SnapMirror contienen reglas que definen su comportamiento. En el caso de las relaciones SnapMirror de tipo async-mirror, o lo que es lo mismo relaciones de protección para disaster recovery, estas reglas se asegurarán de transferir siempre la información actual del volumen origen y, si así lo deseamos, de todos los snapshots que se hayan generado desde la última transferencia de datos.

sábado, 31 de agosto de 2019

ANSIBLE - Ejecución condicional de tareas.

En el último post sobre Ansible vimos como usar bucles para repetir una tarea varias veces en el mismo host.

En ocasiones nos encontraremos con tareas que, o bien no queremos que se ejecuten en un host o bien deben ejecutarse de forma diferente en función de alguna característica del host. Por tanto, en este post, veremos cómo controlar la ejecución de tareas mediante el uso de condicionales.

En general, podemos imaginar muchas situaciones en las que puede ser necesario usar condicionales, como por ejemplo configurar una regla de firewall si un determinado servicio está corriendo, lanzar un comando si un interfaz de red tiene una dirección IP de un rango determinado o instalar un paquete de software en un conjunto de hosts basándonos en la versión del sistema operativo.

Como siempre la forma más sencilla de entenderlo es viendo un ejemplo, así que empecemos con uno simple como crear un fichero en función del sistema operativo del host. Lo importante de este ejemplo es que es necesario obtener los facts de nuestros hosts para poder realizar las comparaciones de manera correcta. Veamos con un ejemplo a lo que me refiero:

Uso de una condición simple.
Al ejecutar este playbook recibimos el siguuiente mensaje de error:

Error en la ejecución de la tarea.
Como vemos en el mensaje de error, ansible indica que no hay un atributo ansible_os_family con el que realizar la comparación y, debido a esto, se produce el fallo de ejecución. Esto es debido a que he especificado que no quiero que se recojan los facts de los hosts sobre los que se ejecuta la tarea, con la línea gather_facts: no, y por tanto ansible no dispondrá de la información contenida en dichas variables. Si cambiamos el playbook para que se recojan los facts de todos los hosts y lo volvemos a ejecutar, ahora el resultado será correcto:

Obteniendo los facts de los hosts la condición se evalúa correctamente.
Por tanto, la primera conclusión que sacamos es que, para usar condiciones basadas en características de los hosts será necesario obtener los facts de los mismos durante la ejecución del playbook o bien tenerlos cacheados, si queremos evitar tener que obtenerlos cada vez que ejecutemos nuestros playbooks.

Un punto interesante a tener en cuenta es que podemos hacer referencia a estas variables de host de dos formas, usando hostvars o bien usando ansible_facts:
  • Mediante el uso de hostvars podemos hacer referencia a una variable de cualquier host, no solamente del host sobre el que se está ejecutando la tarea. Para esto es necesario que, o bien ya se hayan recogido los facts del host referenciado o realicemos caching de facts.
  • Mediante ansible_facts solo haremos referencia a las variables del host sobre el cual se está ejecutando la tarea en ese momento.
En resumen, cualquiera de las dos opciones siguientes son equivalentes para nuestro ejemplo:

Opciones para usar variables de host en condicionales.
Por comodidad y si no vamos a hacer referencia a variables de otros hosts, lo mejor es usar la segunda forma en la que solo usaremos las variables del host en el que estamos ejecutando la tarea pero, en ambos casos, necesitaremos especificar la opción gather_facts a yes.

Este ejemplo tan sencillo nos ha permitido ver cómo podemos usar una condición muy simple, si ahora nos complicamos un poco más veremos que podemos usar operadores lógicos para hacer condiciones más complejas. Siguiendo con el ejemplo anterior, vamos a crear el fichero solo en aquellos hosts CentOS cuya versión sea superior a la 6, con lo cual la condición pasará a ser:

Uso de operadores lógicos en condiciones.
Y al lanzar el playbook, de nuevo creará el fichero correctamente en los hosts donde se cumpla dicha condición.

¿Cúal es la salida si el host no cumple la condición?¿que salida genera Ansible en ese caso? veremos que la tarea para el host se marca como skipping, indicando así que no se ha ejecutado:

Tarea no ejecutada en un host.
En el caso anterior, he fijado en la condición que la versión del sistema operativo debe ser igual a 6 y, al no cumplirse, Ansible no la ejecuta y la marca como skipping.

Para terminar, también podemos crear condiciones basadas en los resultados de la ejecución de otras tareas de nuestros playbooks. Por ejemplo, si necesitamos crear un directorio cuando el número de ficheros contenidos en otro directorio supere un determinado número, podemos hacer algo como lo siguiente:
Condición basada en una variable registrada en otra tarea.


En este caso registramos la salida del comando en la variable num_of_files la cual, al ser un diccionario, contiene en la clave stdout el resultado del comando ejecutado. Realizando la comparación que necesitamos con el valor de esa clave, podemos controlar cuando se ejecuta la tarea de creación del directorio. Así, cuando en el directorio hay más de 8 archivos, la ejecución será:

Resultado de la ejecucióon del playbook anterior.
En resumen, podemos controlar la ejecución de tareas mediante el uso de condiciones, ya sean con variables de los hosts o bien con aquellas que generemos durante la ejecución de tareas. Estas condiciones pueden agruparse mediante operadores lógicos, permitiendo la creación de condiciones más complejas que nos permitirán controlar nuestros playbooks con detalle.

sábado, 6 de julio de 2019

ELK - Introducción a Logstash IV

Tras las entradas anteriores, vamos a crear un pipeline en el que procesaremos dos entradas diferentes procedentes de dos hosts distintos, generadas ambas con syslog_generator, y que enviaremos a Elasticsearch.

La definición de pipelines en Logstash se establece en el fichero pipelines.yml, el cual por defecto tiene la siguiente configuración:

Fichero de configuración pipelines.yml.
Como podemos ver, por defecto hay definido un pipeline denominado main y cuya configuración estará en la ruta indicada en el fichero dado por la opción path.config.

En este fichero podremos indicar opciones de configuración para cada pipeline, así como la ruta en la que se encontrarán los ficheros de configuración de cada uno de ellos. Hay múltiples opciones de configuración disponibles que podremos establecer para cada pipeline de forma individual, siendo algunas de las más interesantes:
  • pipeline.workers. La configuración por defecto de Logstash está optimizada para un único pipeline, con lo que habrá competición por los recursos del sistema si configuramos múltiples pipelines. Cada una de ellas usará un worker por cada un de los cores de CPU disponibles, siendo por tanto recomendable reducir el número de workers de cada pipeline y ajustar el número en función del rendimiento del sistema y CPUs disponibles.
  • queue.type. Logstash utiliza colas basadas en memoria para almacenar los eventos entre las diferentes fases de un pipeline. Como es lógico, en caso de fallo, los datos que se encuentren en memoria se perderán. Para evitar esto, se puede configurar el tipo de cola como persisted para almacenar los eventos en disco, aunque es importante resaltar que determinados plugins de entrada no pueden ser protegidos contra la pérdida de datos, independientemente del tipo de cola usado.
Para este ejemplo vamos a crear dos pipelines diferentes, uno procesará datos de tipo FIXED y otro datos de tipo TEMP, ambos generados por syslog_generator. La estructura de cada uno de estos eventos será:
Tipo FIXED.
Tipo TEMP.
Con todo esto, pasamos a establecer la siguiente configuación de pipelines en Logstash:

Configuración de múltiples pipelines.
Siendo la configuración de cada pipeline la siguiente:

Configuración de pipeline para eventos de tipo TEMP.
Configuración de pipeline para eventos de tipo FIXED.
Como vemos en los ficheros de configuración anteriores, la diferencia principal con lo que habíamos hecho hasta ahora es la sección output de ambos pipelines. En ambos establecemos que el plugin de salida es elasticsearch, que nos permitirá enviar la salida de cada piepline a elasticsearch. Hay varias opciones de configuración disponibles para este plugin, pero de momento solo usaremos la opción que nos permite especificar el host de destino.

También conviene destacar que cada pipeline levanta un listener de entrada en el host Logstah, con lo que es necesario cambiar los puertos en el plugin de entrada si necesitamos usarlo para diferentes orígenes.

Con esta configuración establecida, cuando arrancamos syslog_generator en los nodos fuente, estos comenzarán a enviar datos a Logstash, cada pipeline aplicará el filtro correspondiente y enviará los datos procesados a Elasticsearch.

Al estar usando la configuración por defecto, veremos que se crea un índice identificado por la cadena logstash-YYYY.MM.DD, lo cual podemos comprobar desde Kibana, en la sección Index Management:

Lista de índices disponibles en Elasticsearch.

Para poder trabajar con los documentos de estos índices, solo tenemos que establecer un patrón desde la sección Index Patterns de Kibana, lo que nos permitirá comenzar a trabajar con los datos disponibles. En este caso, la forma más sencilla de crear el patrón sería:

Creación de un index pattern para los datos procesados en Logstash.
Como vemos, al establecer un patrón que coincide con algún índice existente, estos se muestran para que podamos elegir el índice deseado, o bien aplicarlo a todos aquellos que cumplan con el patrón. En el siguiente paso de configuración, Kibana nos preguntará que campo queremos emplear como registro de tiempo para cada evento y seleccionaremos el que nos ofrece por defecto, identificado como @timestamp.
  
Tras la configuración, podremos comprobar cuales son los campos disponibles en la siguiente pantalla:

Lista de todos los campos disponibles en el índice logstash.
Con esta configuración realizada, desde la sección Discover podremos empezar a trabajar con los datos recibidos desde los hosts. Por ejemplo, podemos filtrar la información disponible de todos los documentos y seleccionar los campos tempsensor_temperature y tempsensor_time de entre todos los campos disponibles en los documentos del índice. Para eso solo es necesario que seleccionemos que campos queremos mostrar dentro del apartado Selected fields:

Búsqueda de campos en Kibana.
De este modo, Kibana nos mostrará solamente los campos que hemos seleccionado de todos los documentos del índice en el período de tiempo específicado.

También desde aquí podemos realizar una búsqueda usando el lenguaje de queries de Kibana, para esto solo tenemos que especificar el campo o campos que queremos buscar. Por ejemplo, podemos especificar que busque aquellos documentos en los que existan los campos tempsensor_temperature y tempsensor_time, siendo el resultado el siguiente:

Búsqueda de documentos usando una query.
Al hacer esta búsqueda, ahora solo tendremos aquellos documentos que cumplan la condición de la query que hemos especificado. Del mismo modo, si queremos que nos muestre aquellos documentos donde el campo tempsensor_temperature se encuentre entre dos valores, podemos hacer una query como la siguiente:

Búsqueda de documentos usando una query simple.
Como podemos ver, Kibana solo muestra los documentos que cumplen con la condición establecida en nuestra query.

En resumen, hemos podido ver como configurar Logstash para enviar datos a Elasticsearch y crear un índice en Kibana para poder realizar búsquedas simples en nuestros documentos de prueba.

En las próximas entradas veremos como podemos realizar gráficas, la importancia del mapeo de los campos de nuestros documentos a la hora de crear los índices y como mejorar la configuración de los pipelines.

Ansible - Repetición de tareas mediante bucles.

Hoy vamos a ver, de manera muy simplificada, como podemos usar bucles en playbooks de Ansible para poder repetir la misma tarea varias veces en cada host.

Para generar bucles, Ansible nos proporciona dos comandos diferentes:
  • El comando loop, con lo que podremos crear bucles simples.
  • Los comandos with_*, que se basan en el uso de plugins de búsqueda, con los cuales podremos acceder a datos de fuentes externas. 
Como esto suena un poco raro, partamos de una situación sencilla como la creación de varios ficheros.

Si usamos loop, podemos usar un bucle tan simple como el siguiente:

Bucle simple usando loop.
En este caso está muy claro que podemos crear bucles, de manera realmente sencilla, con solo definir la lista de elementos dentro del bloque loop. Es importante tener en cuenta que no es necesario que usemos los items, como hemos hecho en este caso, la idea es repetir la misma tarea en un host varias veces.

Si nuestros playbooks son grandes y tenemos que usar este tipo de bucles muchas veces, el código puede quedar un poco feo con lo que, una solución que podemos empelar es definir variables que luego usaremos que contengan los elementos de nuestra iteración.  Por ejemplo, podemos definir un fichero de variables para nuestros hosts donde tengamos algo como lo siguiente:

Definimos una variable para las itereaciones.
Y luego podemos usar la variable definida en nuestro playbook del siguiente modo:

Uso de una variable en un bucle.
De este modo podremos iterar sobre listas de números, cadenas de texto, etc... de manera muy simple y, como vemos, incluso usando nuestras propias variables.

Si utilizamos los comandos with_* tendremos más posibilidades al poder usar diccionarios, lo cual nos dará mucha potencia a la hora de realizar tareas. Por ejemplo, siguiendo con el ejemplo anterior, imaginemos que cada uno de los ficheros que necesitamos crear debe estar en una ruta diferente y debe tener unos permisos distintos. Para este caso, podríamos definir una tarea para cada uno de los ficheros que necesitamos crear con cada una de sus características, o bien podemos usar el siguiente bucle:

Definición de un bucle usando diccionarios.
De este modo, cada item que estamos usando en el bucle es un diccionario que tiene dos pares clave-valor cada una de las cuales podemos usar en un parámetro del módulo file. Al ejecutar este playbook el resultado es justo el esperado:

Resultado de la ejecución del playbook con un bucle.
Como es lógico, podemos expandir la lista de items y añadir todas las claves que necesitemos en cada diccionario, así podríamos crear los subdirectorios y luego los ficheros correspondientes con una sola tarea. Para ello, solo tendríamos que definir nuestra lista del siguiente modo:

Expandimos los diccionarios de cada item y creamos los directorios.
Solo hay que tener en cuenta que, en cada iteración del bucle, ansible usa los valores de cada clave y realiza la tarea especificada. En cada caso, los valores de cada elemento son los que nos permiten especificar si se trata de un directorio o de un fichero, así como los permisos del mismo. Esta lista de items, al igual que el caso anterior, podemos definirla en una variable en el fichero group_vars corrspondiente.

Hasta ahora, las listas de items que hemos usado, las hemos creado de manera manual pero, en general puede que se trate de la salida de una tarea que hayamos registrado en una variable o de algún fact de configuración que hemos recogido de nuestro host.

Como ejemplo sencillo de esto último, podemos listar las direcciones IP de las tarjetas de red de un host con un playbook como el siguiente:

Listamos las direcciones IP de todas las tarjetas de un host.
En este playbook construimos una lista para nuestro bucle con el nombre de todas las tarjetas de red del host. Esta lista está contenida en la variable interna de ansible llamada ansible_interfaces, la cual se obtiene con el comando gather_subset que solo contiene la información de red del host.

En este caso, la variable ansible_interfaces es solo una lista con los nombres de los interfaces con lo que, para poder obtener la dirección IP de cada uno de ellos, es necesario que nos construyamos el nombre de la clave del diccionario que contiene toda la información de cada una de las tarjetas de red del host. Esto lo hacemos en la línea:

"{{ hostvars[inventory_hostname]['ansible_'+ item]['ipv4']['address'] }}"

en la cual estamos diciendo, de las variables del host en el cual se está ejecutando la tarea, quiero obtener el valor de la clave ipv4.address de cada interfaz de red, identificados como ansible_"Nombre del interfaz". El nombre del interfaz está contenido en la lista ansible_interfaces, que dependerá del host en el que se ejecute la tarea.

Hay que reconocer que es bastante raro de leer, al menos a mí me lo parece, pero tenemos que pensar que lo que nos devuelve el comando gather_subset es un diccionario con un conjunto de parejas clave-valor para cada interfaz de red. Teniendo esto en cuenta, lo que necesitamos es acceder a la clave que queremos, en este caso ipv4.address, de cada uno de los interfaces de red del host, que están identificados como ansible_"Nombre del interfaz".
Por tanto, hemos visto como podemos hacer bucles para que una tarea se repita varias veces en un host, además de como usar diccionarios para poder especificar las opciones de un módulo en un bucle. También hemos usado las variables e información recogida por Ansible de un host para hacer un bucle simple recorriendo las interface de red de un host.

En el siguiente post sobre Ansible veremos como podemos usar condicionales y lo complicaremos un poco usando bucles.

sábado, 22 de junio de 2019

Cluster ONTAP - Replicación de datos con SnapMirror - Parte I

Hoy, después de mucho tiempo sin publicar nada sobre sistemas NetApp, vamos a revisar como podemos crear una estrategia de disaster recovery para proteger los datos almacenados en sistemas ONTAP. Cómo creo que es necesario que sea bastante detallado, voy a hacer una serie de posts sobre SnapMirror en sistemas cluster ONTAP, siendo este el primero de la serie y donde veremos como establecer la relación de peering entre los clusters y comenzaremos la creación de la relación de SnapMirror.

En un post anterior, nada menos que de 2015, ya explicaba como configurar y usar SnapMirror en sistemas ONTAP 7-mode con lo que, lo que vamos a ver en este nuevo post es similar, pero con las características de la versión cluster ONTAP.

Cómo ya sabemos, la replicación de datos entre sistemas ONTAP se basa en el uso de SnapMirror, con el cual podemos replicar los snapshots de un volumen a una localización de respaldo, desde donde servir los datos replicados en caso de desastre en el site principal. Cómo es lógico, en el caso de estar implementando una solución de backup a disco con SnapVault, ONTAP también está empleando SnapMirror para replicar los snapshots de los volúmenes, pero se establecen una serie de reglas de retención en función de nuestras necesidades de backup.

La principal diferencia que encontramos en cluster ONTAP, es la necesidad de crear una relación entre ambos clusters para poder establecer la relación de SnapMirror entre ellos. Esta medida de seguridad nos asegura que solamente los clusters autorizados podrán establecer una relación SnapMirror para la réplica de datos. Esta relación de peering debe existir también entre las SVMs propietarias de los volúmenes.

Adicionalmente, ya que en cluster ONTAP creamos SVMs para servidr datos mediante diferentes protocolos, podremos establecer relaciones de SnapMirror no solamente entre volúmenes, sino entre las SVMs propietarias de los volúmenes. De este modo, todo o parte de la configuración de la SVM se replicará al sistema secundario, así como los datos de los volúmenes de los que es propietaria la SVM. La replicación de SVMs la trataremos en otro post de la serie en más detalle.

Hay dos modos o motores de replicación diferentes implementados en ONTAP, denominados XDP (eXtended Data Protection) y DP (Data Protection). La diferencia fundamental entre ambos es que, si creamos una relación de SnapMirror de tipo DP, las versiones ONTAP de los sistemas involucrados debe ser la misma, mientras que el modo XDP soporta que las versiones sean diferentes. Este punto, aunque pueda parecer secundario, es importante por sus implicaciones y porque hasta ONTAP 9.3, DP era el modo por defecto empleado al crear una relación SnapMirror.

Para establecer la relación entre los clusters, o peering, es necesario que estos tenga interfaces dedicadas para la comunicación entre clusters, los denominados LIF de tipo intercluster. Estos LIFs deben existir en todos los nodos de los clusters que vayan formar parte de la relación de peering y pueden ser dedicados o compartidos con data LIFs. En resumen, necesitaremos crear tantos LIFs de tipo intercluster como nodos, siendo la configuración la habitual, tanto por CLI como usando OnCommand System Manager, pero especificando que el rol del LIF es intercluster.

Una vez que hemos contado todo el rollo teórico básico, vamos a la parte interesante y configuremos una relación de SnapMirror básica, siendo el punto de partida dos clusters de dos nodos con los LIFs de intercluster ya configurados:

LIFs intercluster configuradas.
LIFs intercluster configuradas.
Una vez creadas las LIFs de tipo intercluster y tras comprobar que existe conectividad y que ningún firewall nos impide el acceso a los puertos necesarios, creamos la relación de peering entre los dos clusters.

En versiones anteriores a ONTAP 9.3 el comando usado para crear la relación de peering era bastante largo, además de que era necesario especificar las direcciones IP de intercluster, con lo que era bastante más cómodo usar OnCommand System Manager para establecer la relación de peering.

A partir de ONTAP 9.3, el comando cluster peer create es mucho más simple y nos permite crear la relación de peering de una forma mucho más sencilla. Solo tenemos que lanzar el comando cluster peer create en el sistema ONTAP de destino, el cual generará una salida incluyendo la passphrase que usaremos para establecer el peering con el cluster de origen. Por ejemplo:

Creamos la relación de peering en el sistema ONTAP de destino.
Aceptamos la relación de peering en el sistema ONTAP de origen.
Como vemos, la creación de la relación de peering es muy simple y es importante tener en cuenta que la passphrase generada es única para cada ejecución del comando y válida solamente para un cluster. Solamente en el caso del sistema origen de nuestros datos es necesario que especifiquemos las direcciones IP de los LIFs intercluster del sistema ONTAP con el que queremos establecer la relación de peering.

Ahora podemos comprobar que la relación de peering es correcta entre ambos clusters:

Estado de la relación de peering en un cluster.
Estado de la relación de peering en el otro cluster.
Ahora establecemos la relación de peering entre las SVMs implicadas, para lo cual solo tenemos que lanzar el comando siguiente desde nuestro cluster ONTAP destino:

Creamos la relación de peering entre las SMVs. Es necesario especificar el tipo de aplicación.
Este comando crea una petición de peering que es necesario que aceptemos en el sistema ONTAP origen:
La petición de peering de SVMs pendieente en origen.
La petición de peering de SVMs pendieente en destino.
Para aceptar la petición de peering solo tenemos que lanzar el comando vserver peer accept:

Aceptamos la petición de peering entre las SVMs en el sistema ONTAP origen.
Tras lanzarlo podemos comprobar cómo la relación entre ambas SVMs pasa a estado peered.

Relación entre SVMs en estado peered.
En uno de los posts siguientes de esta serie veremos como podemos crear la relación de SnapMirror en un solo paso con el comando snapmirror protect, pero creo que para entender mejor ese comando y todo lo que implica, vamos a crear la relación de SnapMirror paso a paso.

Crear una relación de SnapMirror entre dos clusters implica realizar una serie de pasos simples los cuales, en resumen, son los siguientes:
  1. Es necesario crear en el sistema destino un volumen que contendrá los datos del volumen origen. Cómo es lógico el tamaño del volumen destino debe ser igual o superior al de origen.
  2. Creamos una programación o schedule para especificar cuando queremos que se realicen las actualizaciones de los datos entre los volúmenes de la relación SnapMirror.
  3. Especificamos la política de replicación que queremos emplear. De esta manera establecemos que snapshots queremos transferir entre los clusters y como queremos hacerlo.
  4. Creamos la relación de SnapMirror entre los volúmenes, en función de los pasos anteriores.
  5. Inicializamos la relación de SnapMirror, realizando la copia de datos inicial que se denomina transferencia baseline.
Siguiendo los pasos anteriores, si quiero proteger el volumen VOL_nfsserver1_datos de la SVM SVM_nfsserver1, primero debo crear un volumen en el sistema destino. Por ejemplo, podemos hacerlo del siguiente modo y usando un nombre descriptivo que nos permita distinguir rápidamente que es un volumen replicado. Además es importante indicar que el tipo de volumen es DP, indicando así que se va a utilizar para destino de SnapMirror:

Creamos el volumen destino de tipo DP.
Aunque en este ejemplo no lo hemos especificado, el volumen puede ser thin provisioned, con lo que solo tenemos que especificar la opción space-guarantee a none en el momento de crearlo.

Ahora establecemos la programación de la tarea de replicación, para lo cual podemos crear uno o usar uno de los ya predefinidos. Suponiendo que necesitamos que nuestros datos se repliquen de lunes a viernes desde las 8:00, cada 3 horas, hasta las 23:00 horas, nuestro schedule podría ser el siguiente:

Creamos la programación de nuestra tarea de replicación.
Este schedule lo crearemos en el cluster destino ya que, es importante recordar que SnapMirror siempre se inicia en el sistema ONTAP destino el cual hace un pull de los datos desde el sistema origen.

Ahora necesitamos crear la política de replicación o bien usar una de las predefinidas. En este caso, y por sencillez, vamos a usar la política MirrorAllSnapshots, mediante la cual SnapMirror trnasferirá todos los snapshots, tanto en la inicialización cómo en las actualizaciones. Así conseguimos que los snapshots que realice el sistema ONTAP origen también se transfieran al sistema destino.

Con los datos anteriores, el comando que debemos utilizar en el cluster destino para establecer la relación de SnapMirror es el siguiente:

Creamos la relación de SnapMirror entre los dos volúmenes.
Ahora podemos ver que la relación de SnapMirror esta creada pero no inicializada, todavía no se ha transferido ningún dato entre ambos volúmenes:

La relación de SnapMirror creada pero no inicializada.
Para inicializarla es necesario realizar la primera copia, llamada baseline transfer, entre ambos volúmenes. De esta manera todos los datos del volumen, así como todos los snapshots existentes, se transferirán al volumen destino ya que la política que hemos especificado es MirrorAllSnapshots. Para lanzar la transferencia baseline, y por tanto inicializar de forma efectiva la relación de SnapMirror, solo tenemos que lanzar el siguiente comando desde el sistema ONTAP destino:

Lanzamos la transferencia baseline entre ambos volúmenes.
Esta transferencia inicial tardará más o menos timepo, dependiendo de la cantidad de información que deba transferir.

Proceso de inicialización de la relación SnapMirror.
Una vez terminado el proceso de transferencia inicial, podremos comprobar que la relación SnapMirror ha pasado a estado Snapmirrored y, más importante todavía, que todos los datos, así como los snapshots del volumen origen, se han replicado al volumen destino.

La relación SnapMirror ya establecida entre ambos volúmenes.
Lista de snapshots en el volumen destino.
Con esto ya hemos establecido la relación de SnapMirror entre nuestros dos sistemas ONTAP y hemos protegido un volumen. En las siguientes entradas de esta serie veremos más en detalle los tipos de políticas existentes, como establecer relaciones de DR para SVMs completas y como usar los datos replicados en caso de desastre.