apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
name: my-topic
labels:
strimzi.io/cluster: my-cluster
spec:
partitions: 1
replicas: 1
# ...
Strimzi simplifies the process of running Apache Kafka in a Kubernetes cluster.
This guide is intended as a starting point for building an understanding of Strimzi. The guide introduces some of the key concepts behind Kafka, which is central to Strimzi, explaining briefly the purpose of Kafka components. Configuration points are outlined, including options to secure and monitor Kafka. A distribution of Strimzi provides the files to deploy and manage a Kafka cluster, as well as example files for configuration and monitoring of your deployment.
A typical Kafka deployment is described, as well as the tools used to deploy and manage Kafka.
The underlying data stream-processing capabilities and component architecture of Kafka can deliver:
Microservices and other applications to share data with extremely high throughput and low latency
Message ordering guarantees
Message rewind/replay from data storage to reconstruct an application state
Message compaction to remove old records when using a key-value log
Horizontal scalability in a cluster configuration
Replication of data to control fault tolerance
Retention of high volumes of data for immediate access
Kafka’s capabilities make it suitable for:
Event-driven architectures
Event sourcing to capture changes to the state of an application as a log of events
Message brokering
Website activity tracking
Operational monitoring through metrics
Log collection and aggregation
Commit logs for distributed systems
Stream processing so that applications can respond to data in real time
Strimzi provides container images and Operators for running Kafka on Kubernetes. Strimzi Operators are fundamental to the running of Strimzi. The Operators provided with Strimzi are purpose-built with specialist operational knowledge to effectively manage Kafka.
Operators simplify the process of:
Deploying and running Kafka clusters
Deploying and running Kafka components
Configuring access to Kafka
Securing access to Kafka
Upgrading Kafka
Managing brokers
Creating and managing topics
Creating and managing users
Apache Kafka components are provided for deployment to Kubernetes with the Strimzi distribution. The Kafka components are generally run as clusters for availability.
A typical deployment incorporating Kafka components might include:
Kafka cluster of broker nodes
ZooKeeper cluster of replicated ZooKeeper instances
Kafka Connect cluster for external data connections
Kafka MirrorMaker cluster to mirror the Kafka cluster in a secondary cluster
Kafka Exporter to extract additional Kafka metrics data for monitoring
Kafka Bridge to make HTTP-based requests to the Kafka cluster
Not all of these components are mandatory, though you need Kafka and ZooKeeper as a minimum. Some components can be deployed without Kafka, such as MirrorMaker or Kafka Connect.
A cluster of Kafka brokers handles delivery of messages.
A broker uses Apache ZooKeeper for storing configuration data and for cluster coordination. Before running Apache Kafka, an Apache ZooKeeper cluster has to be ready.
Each of the other Kafka components interact with the Kafka cluster to perform specific roles.
Apache ZooKeeper is a core dependency for Kafka as it provides a cluster coordination service, storing and tracking the status of brokers and consumers. ZooKeeper is also used for controller election.
Kafka Connect is an integration toolkit for streaming data between Kafka brokers and other systems using Connector plugins. Kafka Connect provides a framework for integrating Kafka with an external data source or target, such as a database, for import or export of data using connectors. Connectors are plugins that provide the connection configuration needed.
A source connector pushes external data into Kafka.
A sink connector extracts data out of Kafka
External data is translated and transformed into the appropriate format.
You can deploy Kafka Connect with build
configuration that automatically builds a container image with the connector plugins you require for your data connections.
Kafka MirrorMaker replicates data between two Kafka clusters, within or across data centers.
MirrorMaker takes messages from a source Kafka cluster and writes them to a target Kafka cluster.
Kafka Bridge provides an API for integrating HTTP-based clients with a Kafka cluster.
Kafka Exporter extracts data for analysis as Prometheus metrics, primarily data relating to offsets, consumer groups, consumer lag and topics. Consumer lag is the delay between the last message written to a partition and the message currently being picked up from that partition by a consumer
Apache Kafka is an open-source distributed publish-subscribe messaging system for fault-tolerant real-time data feeds.
For more information about Apache Kafka, see the Apache Kafka documentation.
To maximise your experience of using Strimzi, you need to understand how Kafka operates as a message broker.
A Kafka cluster comprises multiple brokers. Brokers contain topics that receive and store data. Topics are split by partitions, where the data is written. Partitions are replicated across topics for fault tolerance.
A broker, sometimes referred to as a server or node, orchestrates the storage and passing of messages.
A topic provides a destination for the storage of data. Each topic is split into one or more partitions.
A group of broker instances.
The number of topic partitions is defined by a topic partition count.
A partition leader handles all producer requests for a topic.
A partition follower replicates the partition data of a partition leader, optionally handling consumer requests.
Topics use a replication factor to configure the number of replicas of each partition within the cluster. A topic comprises at least one partition.
An in-sync replica has the same number of messages as the leader. Configuration defines how many replicas must be in-sync to be able to produce messages, ensuring that a message is committed only after it has been successfully copied to the replica partition. In this way, if the leader fails the message is not lost.
In the Kafka brokers and topics diagram, we can see each numbered partition has a leader and two followers in replicated topics.
Producers and consumers send and receive messages (publish and subscribe) through brokers. Messages comprise an optional key and a value that contains the message data, plus headers and related metadata. The key is used to identify the subject of the message, or a property of the message. Messages are delivered in batches, and batches and records contain headers and metadata that provide details that are useful for filtering and routing by clients, such as the timestamp and offset position for the record.
A producer sends messages to a broker topic to be written to the end offset of a partition. Messages are written to partitions by a producer on a round robin basis, or to a specific partition based on the message key.
A consumer subscribes to a topic and reads messages according to topic, partition and offset.
Consumer groups are used to share a typically large data stream generated by multiple producers from a given topic.
Consumers are grouped using a group.id
, allowing messages to be spread across the members.
Consumers within a group do not read data from the same partition, but can receive data from one or more partitions.
Offsets describe the position of messages within a partition. Each message in a given partition has a unique offset, which helps identify the position of a consumer within the partition to track the number of records that have been consumed.
Committed offsets are written to an offset commit log.
A __consumer_offsets
topic stores information on committed offsets, the position of last and next offset, according to consumer group.
Kafka Connect is an integration toolkit for streaming data between Kafka brokers and other systems. The other system is typically an external data source or target, such as a database.
Kafka Connect uses a plugin architecture. Plugins allow connections to other systems and provide additional configuration to manipulate data. Plugins include connectors and other components, such as data converters and transforms. A connector operates with a specific type of external system. Each connector defines a schema for its configuration. You supply the configuration to Kafka Connect to create a connector instance within Kafka Connect. Connector instances then define a set of tasks for moving data between systems.
Strimzi operates Kafka Connect in distributed mode, distributing data streaming tasks across one or more worker pods. A Kafka Connect cluster comprises a group of worker pods. Each connector is instantiated on a single worker. Each connector comprises one or more tasks that are distributed across the group of workers. Distribution across workers permits highly scalable pipelines.
Workers convert data from one format into another format that’s suitable for the source or target system. Depending on the configuration of the connector instance, workers might also apply transforms (also known as Single Message Transforms, or SMTs). Transforms adjust messages, such as filtering certain data, before they are converted. Kafka Connect has some built-in transforms, but other transformations can be provided by plugins if necessary.
Kafka Connect uses connector instances to integrate with other systems to stream data.
Kafka Connect loads existing connector instances on start up and distributes data streaming tasks and connector configuration across worker pods. Workers run the tasks for the connector instances. Each worker runs as a separate pod to make the Kafka Connect cluster more fault tolerant. If there are more tasks than workers, workers are assigned multiple tasks. If a worker fails, its tasks are automatically assigned to active workers in the Kafka Connect cluster.
The main Kafka Connect components used in streaming data are as follows:
Connectors to create tasks
Tasks to move data
Workers to run tasks
Transforms to manipulate data
Converters to convert data
Connectors can be one of the following type:
Source connectors that push data into Kafka
Sink connectors that extract data out of Kafka
Plugins provide the implementation for Kafka Connect to run connector instances. Connector instances create the tasks required to transfer data in and out of Kafka. The Kafka Connect runtime orchestrates the tasks to split the work required between the worker pods.
MirrorMaker 2.0 also uses the Kafka Connect framework. In this case, the external data system is another Kafka cluster. Specialized connectors for MirrorMaker 2.0 manage data replication between source and target Kafka clusters.
|
Note
|
In addition to the MirrorMaker 2.0 connectors, Kafka provides two connectors as examples:
|
The following source connector diagram shows the process flow for a source connector that streams records from an external data system. A Kafka Connect cluster might operate source and sink connectors at the same time. Workers are running in distributed mode in the cluster. Workers can run one or more tasks for more than one connector instance.
A plugin provides the implementation artifacts for the source connector
A single worker initiates the source connector instance
The source connector creates the tasks to stream data
Tasks run in parallel to poll the external data system and return records
Transforms adjust the records, such as filtering or relabelling them
Converters put the records into a format suitable for Kafka
The source connector is managed using KafkaConnectors or the Kafka Connect API
The following sink connector diagram shows the process flow when streaming data from Kafka to an external data system.
A plugin provides the implementation artifacts for the sink connector
A single worker initiates the sink connector instance
The sink connector creates the tasks to stream data
Tasks run in parallel to poll Kafka and return records
Converters put the records into a format suitable for the external data system
Transforms adjust the records, such as filtering or relabelling them
The sink connector is managed using KafkaConnectors or the Kafka Connect API
Data transfer orchestrated by the Kafka Connect runtime is split into tasks that run in parallel. A task is started using the configuration supplied by a connector instance. Kafka Connect distributes the task configurations to workers, which instantiate and execute tasks.
A source connector task polls the external data system and returns a list of records that a worker sends to the Kafka brokers.
A sink connector task receives Kafka records from a worker for writing to the external data system.
For sink connectors, the number of tasks created relates to the number of partitions being consumed.
For source connectors, how the source data is partitioned is defined by the connector.
You can control the maximum number of tasks that can run in parallel by setting tasksMax
in the connector configuration.
The connector might create fewer tasks than the maximum setting.
For example, the connector might create fewer tasks if it’s not possible to split the source data into that many partitions.
|
Note
|
In the context of Kafka Connect, a partition can mean a topic partition or a shard of data in an external system. |
Workers employ the connector configuration deployed to the Kafka Connect cluster. The configuration is stored in an internal Kafka topic used by Kafka Connect. Workers also run connectors and their tasks.
A Kafka Connect cluster contains a group of workers with the same group.id
.
The ID identifies the cluster within Kafka.
The ID is assigned in the worker configuration through the KafkaConnect
resource.
Worker configuration also specifies the names of internal Kafka Connect topics.
The topics store connector configuration, offset, and status information.
The group ID and names of these topics must also be unique to the Kafka Connect cluster.
Workers are assigned one or more connector instances and tasks.
The distributed approach to deploying Kafka Connect is fault tolerant and scalable.
If a worker pod fails, the tasks it was running are reassigned to active workers.
You can add to a group of worker pods through configuration of the replicas
property in the KafkaConnect
resource.
Kafka Connect translates and transforms external data. Single-message transforms change messages into a format suitable for the target destination. For example, a transform might insert or rename a field. Transforms can also filter and route data. Plugins contain the implementation required for workers to perform one or more transformations.
Source connectors apply transforms before converting data into a format supported by Kafka.
Sink connectors apply transforms after converting data into a format suitable for an external data system.
A transform comprises a set of Java class files packaged in a JAR file for inclusion in a connector plugin. Kafka Connect provides a set of standard transforms, but you can also create your own.
When a worker receives data, it converts the data into an appropriate format using a converter.
You specify converters for workers in the worker config
in the KafkaConnect
resource.
Kafka Connect can convert data to and from formats supported by Kafka, such as JSON or Avro. It also supports schemas for structuring data. If you are not converting data into a structured format, you don’t need to enable schemas.
|
Note
|
You can also specify converters for specific connectors to override the general Kafka Connect worker configuration that applies to all workers. |
The Kafka Bridge provides a RESTful interface that allows HTTP-based clients to interact with a Kafka cluster. It offers the advantages of a web API connection to Strimzi, without the need for client applications to interpret the Kafka protocol.
The API has two main resources — consumers
and topics
— that are exposed and made accessible through endpoints to interact with consumers and producers in your Kafka cluster. The resources relate only to the Kafka Bridge, not the consumers and producers connected directly to Kafka.
The Kafka Bridge supports HTTP requests to a Kafka cluster, with methods to:
Send messages to a topic.
Retrieve messages from topics.
Retrieve a list of partitions for a topic.
Create and delete consumers.
Subscribe consumers to topics, so that they start receiving messages from those topics.
Retrieve a list of topics that a consumer is subscribed to.
Unsubscribe consumers from topics.
Assign partitions to consumers.
Commit a list of consumer offsets.
Seek on a partition, so that a consumer starts receiving messages from the first or last offset position, or a given offset position.
The methods provide JSON responses and HTTP response code error handling. Messages can be sent in JSON or binary formats.
Clients can produce and consume messages without the requirement to use the native Kafka protocol.
To view the API documentation, including example requests and responses, see Using the Strimzi Kafka Bridge.
You can use the Kafka Bridge to integrate both internal and external HTTP client applications with your Kafka cluster.
Internal clients are container-based HTTP clients running in the same Kubernetes cluster as the Kafka Bridge itself.
Internal clients can access the Kafka Bridge on the host and port defined in the KafkaBridge
custom resource.
External clients are HTTP clients running outside the Kubernetes cluster in which the Kafka Bridge is deployed and running. External clients can access the Kafka Bridge through an OpenShift Route, a loadbalancer service, or using an Ingress.
Strimzi supports Kafka using Operators to deploy and manage the components and dependencies of Kafka to Kubernetes.
Operators are a method of packaging, deploying, and managing a Kubernetes application. Strimzi Operators extend Kubernetes functionality, automating common and complex tasks related to a Kafka deployment. By implementing knowledge of Kafka operations in code, Kafka administration tasks are simplified and require less manual intervention.
Strimzi provides Operators for managing a Kafka cluster running within a Kubernetes cluster.
Deploys and manages Apache Kafka clusters, Kafka Connect, Kafka MirrorMaker, Kafka Bridge, Kafka Exporter, Cruise Control, and the Entity Operator
Comprises the Topic Operator and User Operator
Manages Kafka topics
Manages Kafka users
The Cluster Operator can deploy the Topic Operator and User Operator as part of an Entity Operator configuration at the same time as a Kafka cluster.
Strimzi uses the Cluster Operator to deploy and manage clusters. By default, when you deploy Strimzi a single Cluster Operator replica is deployed. You can add replicas with leader election so that additional Cluster Operators are on standby in case of disruption.
The Cluster Operator manages the clusters of the following Kafka components:
Kafka (including ZooKeeper, Entity Operator, Kafka Exporter, and Cruise Control)
Kafka Connect
Kafka MirrorMaker
Kafka Bridge
The clusters are deployed using custom resources.
For example, to deploy a Kafka cluster:
A Kafka
resource with the cluster configuration is created within the Kubernetes cluster.
The Cluster Operator deploys a corresponding Kafka cluster, based on what is declared in the Kafka
resource.
The Cluster Operator can also deploy the following Strimzi operators through configuration of the Kafka
resource:
Topic Operator to provide operator-style topic management through KafkaTopic
custom resources
User Operator to provide operator-style user management through KafkaUser
custom resources
The Topic Operator and User Operator function within the Entity Operator on deployment.
You can use the Cluster Operator with a deployment of Strimzi Drain Cleaner to help with pod evictions. By deploying the Strimzi Drain Cleaner, you can use the Cluster Operator to move Kafka pods instead of Kubernetes. Strimzi Drain Cleaner annotates pods being evicted with a rolling update annotation. The annotation informs the Cluster Operator to perform the rolling update.
The Topic Operator provides a way of managing topics in a Kafka cluster through Kubernetes resources.
The role of the Topic Operator is to keep a set of KafkaTopic
Kubernetes resources describing Kafka topics in-sync with corresponding Kafka topics.
Specifically, if a KafkaTopic
is:
Created, the Topic Operator creates the topic
Deleted, the Topic Operator deletes the topic
Changed, the Topic Operator updates the topic
Working in the other direction, if a topic is:
Created within the Kafka cluster, the Operator creates a KafkaTopic
Deleted from the Kafka cluster, the Operator deletes the KafkaTopic
Changed in the Kafka cluster, the Operator updates the KafkaTopic
This allows you to declare a KafkaTopic
as part of your application’s deployment and the Topic Operator will take care of creating the topic for you.
Your application just needs to deal with producing or consuming from the necessary topics.
The Topic Operator maintains information about each topic in a topic store, which is continually synchronized with updates from Kafka topics or Kubernetes KafkaTopic
custom resources.
Updates from operations applied to a local in-memory topic store are persisted to a backup topic store on disk.
If a topic is reconfigured or reassigned to other brokers, the KafkaTopic
will always be up to date.
The User Operator manages Kafka users for a Kafka cluster by watching for KafkaUser
resources that describe Kafka users,
and ensuring that they are configured properly in the Kafka cluster.
For example, if a KafkaUser
is:
Created, the User Operator creates the user it describes
Deleted, the User Operator deletes the user it describes
Changed, the User Operator updates the user it describes
Unlike the Topic Operator, the User Operator does not sync any changes from the Kafka cluster with the Kubernetes resources. Kafka topics can be created by applications directly in Kafka, but it is not expected that the users will be managed directly in the Kafka cluster in parallel with the User Operator.
The User Operator allows you to declare a KafkaUser
resource as part of your application’s deployment.
You can specify the authentication and authorization mechanism for the user.
You can also configure user quotas that control usage of Kafka resources to ensure, for example, that a user does not monopolize access to a broker.
When the user is created, the user credentials are created in a Secret
.
Your application needs to use the user and its credentials for authentication and to produce or consume messages.
In addition to managing credentials for authentication, the User Operator also manages authorization rules by including a description of the user’s access rights in the KafkaUser
declaration.
You can enable and disable some features of operators using feature gates.
Feature gates are set in the operator configuration and have three stages of maturity: alpha, beta, or General Availability (GA).
For more information, see Feature gates.
A deployment of Kafka components to a Kubernetes cluster using Strimzi is highly configurable through the application of custom resources. Custom resources are created as instances of APIs added by Custom resource definitions (CRDs) to extend Kubernetes resources.
CRDs act as configuration instructions to describe the custom resources in a Kubernetes cluster, and are provided with Strimzi for each Kafka component used in a deployment, as well as users and topics. CRDs and custom resources are defined as YAML files. Example YAML files are provided with the Strimzi distribution.
CRDs also allow Strimzi resources to benefit from native Kubernetes features like CLI accessibility and configuration validation.
In this section we look at how Kafka components are configured through custom resources, starting with common configuration points and then important configuration considerations specific to components.
Strimzi provides example configuration files, which can serve as a starting point when building your own Kafka component configuration for deployment.
After a new custom resource type is added to your cluster by installing a CRD, you can create instances of the resource based on its specification.
The custom resources for Strimzi components have common configuration properties, which are defined under spec
.
In this fragment from a Kafka topic custom resource, the apiVersion
and kind
properties identify the associated CRD.
The spec
property shows configuration that defines the number of partitions and replicas for the topic.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
name: my-topic
labels:
strimzi.io/cluster: my-cluster
spec:
partitions: 1
replicas: 1
# ...
There are many additional configuration options that can be incorporated into a YAML definition, some common and some specific to a particular component.
Some of the configuration options common to resources are described here. Security and metrics collection might also be adopted where applicable.
Bootstrap servers are used for host/port connection to a Kafka cluster for:
Kafka Connect
Kafka Bridge
Kafka MirrorMaker producers and consumers
You request CPU and memory resources for components. Limits specify the maximum resources that can be consumed by a given container.
Resource requests and limits for the Topic Operator and User Operator are set in the Kafka
resource.
You define the logging level for the component. Logging can be defined directly (inline) or externally using a config map.
Healthcheck configuration introduces liveness and readiness probes to know when to restart a container (liveness) and when a container can accept traffic (readiness).
JVM options provide maximum and minimum memory allocation to optimize the performance of the component according to the platform it is running on.
Pod schedules use affinity/anti-affinity rules to determine under what circumstances a pod is scheduled onto a node.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
metadata:
name: my-cluster
spec:
# ...
bootstrapServers: my-cluster-kafka-bootstrap:9092
resources:
requests:
cpu: 12
memory: 64Gi
limits:
cpu: 12
memory: 64Gi
logging:
type: inline
loggers:
connect.root.logger.level: "INFO"
readinessProbe:
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
initialDelaySeconds: 15
timeoutSeconds: 5
jvmOptions:
"-Xmx": "2g"
"-Xms": "2g"
template:
pod:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- fast-network
# ...
A kafka cluster comprises one or more brokers. For producers and consumers to be able to access topics within the brokers, Kafka configuration must define how data is stored in the cluster, and how the data is accessed. You can configure a Kafka cluster to run with multiple broker nodes across racks.
Kafka and ZooKeeper store data on disks.
Strimzi requires block storage provisioned through StorageClass
.
The file system format for storage must be XFS or EXT4.
Three types of data storage are supported:
Ephemeral storage stores data for the lifetime of an instance. Data is lost when the instance is restarted.
Persistent storage relates to long-term data storage independent of the lifecycle of the instance.
JBOD allows you to use multiple disks to store commit logs in each broker.
The disk capacity used by an existing Kafka cluster can be increased if supported by the infrastructure.
Listeners configure how clients connect to a Kafka cluster.
By specifying a unique name and port for each listener within a Kafka cluster, you can configure multiple listeners.
The following types of listener are supported:
Internal listeners for access within Kubernetes
External listeners for access outside of Kubernetes
You can enable TLS encryption for listeners, and configure authentication.
Internal listeners are specified using an internal
type.
External listeners expose Kafka by specifying an external type
:
route
to use OpenShift routes and the default HAProxy router
loadbalancer
to use loadbalancer services
nodeport
to use ports on Kubernetes nodes
ingress
to use Kubernetes Ingress and the NGINX Ingress Controller for Kubernetes
If you are using OAuth 2.0 for token-based authentication, you can configure listeners to use the authorization server.
Rack awareness is a configuration feature that distributes Kafka broker pods and topic replicas across racks, which represent data centers or racks in data centers, or availability zones.
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-cluster
spec:
kafka:
# ...
listeners:
- name: tls
port: 9093
type: internal
tls: true
authentication:
type: tls
- name: external1
port: 9094
type: route
tls: true
authentication:
type: tls
# ...
storage:
type: persistent-claim
size: 10000Gi
# ...
rack:
topologyKey: topology.kubernetes.io/zone
# ...
To set up MirrorMaker, a source and target (destination) Kafka cluster must be running.
You can use Strimzi with MirrorMaker 2.0, although the earlier version of MirrorMaker continues to be supported.
MirrorMaker 2.0 consumes messages from a source Kafka cluster and writes them to a target Kafka cluster.
MirrorMaker 2.0 uses:
Source cluster configuration to consume data from the source cluster
Target cluster configuration to output data to the target cluster
MirrorMaker 2.0 is based on the Kafka Connect framework, connectors managing the transfer of data between clusters.
You configure MirrorMaker 2.0 to define the Kafka Connect deployment, including the connection details of the source and target clusters, and then run a set of MirrorMaker 2.0 connectors to make the connection.
MirrorMaker 2.0 consists of the following connectors:
MirrorSourceConnector
The source connector replicates topics from a source cluster to a target cluster. It also replicates ACLs and is necessary for the MirrorCheckpointConnector
to run.
MirrorCheckpointConnector
The checkpoint connector periodically tracks offsets. If enabled, it also synchronizes consumer group offsets between the source and target cluster.
MirrorHeartbeatConnector
The heartbeat connector periodically checks connectivity between the source and target cluster.
|
Note
|
If you are using the User Operator to manage ACLs, ACL replication through the connector is not possible. |
The process of mirroring data from a source cluster to a target cluster is asynchronous. Each MirrorMaker 2.0 instance mirrors data from one source cluster to one target cluster. You can use more than one MirrorMaker 2.0 instance to mirror data between any number of clusters.
By default, a check for new topics in the source cluster is made every 10 minutes.
You can change the frequency by adding refresh.topics.interval.seconds
to the source connector configuration.
You can use MirrorMaker 2.0 in active/passive or active/active cluster configurations.
An active/active configuration has two active clusters replicating data bidirectionally. Applications can use either cluster. Each cluster can provide the same data. In this way, you can make the same data available in different geographical locations. As consumer groups are active in both clusters, consumer offsets for replicated topics are not synchronized back to the source cluster.
An active/passive configuration has an active cluster replicating data to a passive cluster. The passive cluster remains on standby. You might use the passive cluster for data recovery in the event of system failure.
The expectation is that producers and consumers connect to active clusters only. A MirrorMaker 2.0 cluster is required at each target destination.
The MirrorMaker 2.0 architecture supports bidirectional replication in an active/active cluster configuration.
Each cluster replicates the data of the other cluster using the concept of source and remote topics. As the same topics are stored in each cluster, remote topics are automatically renamed by MirrorMaker 2.0 to represent the source cluster. The name of the originating cluster is prepended to the name of the topic.
By flagging the originating cluster, topics are not replicated back to that cluster.
The concept of replication through remote topics is useful when configuring an architecture that requires data aggregation. Consumers can subscribe to source and remote topics within the same cluster, without the need for a separate aggregation cluster.
The MirrorMaker 2.0 architecture supports unidirectional replication in an active/passive cluster configuration.
You can use an active/passive cluster configuration to make backups or migrate data to another cluster. In this situation, you might not want automatic renaming of remote topics.
You can override automatic renaming by adding IdentityReplicationPolicy
to the source connector configuration.
With this configuration applied, topics retain their original names.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaMirrorMaker2
metadata:
name: my-mirror-maker2
spec:
version: 3.3.1
connectCluster: "my-cluster-target"
clusters:
- alias: "my-cluster-source"
bootstrapServers: my-cluster-source-kafka-bootstrap:9092
- alias: "my-cluster-target"
bootstrapServers: my-cluster-target-kafka-bootstrap:9092
mirrors:
- sourceCluster: "my-cluster-source"
targetCluster: "my-cluster-target"
sourceConnector: {}
topicsPattern: ".*"
groupsPattern: "group1|group2|group3"
The earlier version of MirrorMaker uses producers and consumers to replicate data across clusters.
MirrorMaker uses:
Consumer configuration to consume data from the source cluster
Producer configuration to output data to the target cluster
Consumer and producer configuration includes any authentication and encryption settings.
The include
field defines the topics to mirror from a source to a target cluster.
The consumer group ID for a MirrorMaker consumer so that messages consumed are assigned to a consumer group.
A value to determine the number of consumers in a consumer group that consume a message in parallel.
An offset commit interval to set the time between consuming and committing a message.
You can define whether a message send failure is ignored or MirrorMaker is terminated and recreated.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaMirrorMaker
metadata:
name: my-mirror-maker
spec:
# ...
consumer:
bootstrapServers: my-source-cluster-kafka-bootstrap:9092
groupId: "my-group"
numStreams: 2
offsetCommitInterval: 120000
# ...
producer:
# ...
abortOnSendFailure: false
# ...
include: "my-topic|other-topic"
# ...
Use Strimzi’s KafkaConnect
resource to quickly and easily create new Kafka Connect clusters.
When you deploy Kafka Connect using the KafkaConnect
resource,
you specify bootstrap server addresses (in spec.bootstrapServers
) for connecting to a Kafka cluster.
You can specify more than one address in case a server goes down.
You also specify the authentication credentials and TLS encryption certificates to make a secure connection.
|
Note
|
The Kafka cluster doesn’t need to be managed by Strimzi or deployed to a Kubernetes cluster. |
You can also use the KafkaConnect
resource to specify the following:
Plugin configuration to build a container image that includes the plugins to make connections
Configuration for the worker pods that belong to the Kafka Connect cluster
An annotation to enable use of the KafkaConnector
resource to manage plugins
The Cluster Operator manages Kafka Connect clusters deployed using the KafkaConnect
resource and connectors created using the KafkaConnector
resource.
Plugins provide the implementation for creating connector instances. When a plugin is instantiated, configuration is provided for connection to a specific type of external data system. Plugins provide a set of one or more JAR files that define a connector and task implementation for connecting to a given kind of data source. Plugins for many external systems are available for use with Kafka Connect. You can also create your own plugins.
The configuration describes the source input data and target output data to feed into and out of Kafka Connect. For a source connector, external source data must reference specific topics that will store the messages. The plugins might also contain the libraries and files needed to transform the data.
A Kafka Connect deployment can have one or more plugins, but only one version of each plugin.
You can create a custom Kafka Connect image that includes your choice of plugins. You can create the image in two ways:
To create the container image automatically, you specify the plugins to add to your Kafka Connect cluster using the build
property of the KafkaConnect
resource.
Strimzi automatically downloads and adds the plugin artifacts to a new container image.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
metadata:
name: my-connect-cluster
annotations:
strimzi.io/use-connector-resources: "true"
spec:
# ...
build: (1)
output: (2)
type: docker
image: my-registry.io/my-org/my-connect-cluster:latest
pushSecret: my-registry-credentials
plugins: (3)
- name: debezium-postgres-connector
artifacts:
- type: tgz
url: https://ARTIFACT-ADDRESS.tgz
sha512sum: HASH-NUMBER-TO-VERIFY-ARTIFACT
# ...
# ...
Build configuration properties for building a container image with plugins automatically.
Configuration of the container registry where new images are pushed. The output
properties describe the type and name of the image, and optionally the name of the secret containing the credentials needed to access the container registry.
List of plugins and their artifacts to add to the new container image. The plugins
properties describe the type of artifact and the URL from which the artifact is downloaded. Each plugin must be configured with at least one artifact. Additionally, you can specify a SHA-512 checksum to verify the artifact before unpacking it.
If you are using a Dockerfile to build an image, you can use Strimzi’s latest container image as a base image to add your plugin configuration file.
FROM quay.io/strimzi/kafka:0.32.0-kafka-3.3.1
USER root:root
COPY ./my-plugins/ /opt/kafka/plugins/
USER 1001
You specify the configuration for workers in the config
property of the KafkaConnect
resource.
A distributed Kafka Connect cluster has a group ID and a set of internal configuration topics.
group.id
offset.storage.topic
config.storage.topic
status.storage.topic
Kafka Connect clusters are configured by default with the same values for these properties. Kafka Connect clusters cannot share the group ID or topic names as it will create errors. If multiple different Kafka Connect clusters are used, these settings must be unique for the workers of each Kafka Connect cluster created.
The names of the connectors used by each Kafka Connect cluster must also be unique.
In the following example worker configuration, JSON converters are specified. A replication factor is set for the internal Kafka topics used by Kafka Connect. This should be at least 3 for a production environment. Changing the replication factor after the topics have been created will have no effect.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
# ...
spec:
config:
# ...
group.id: my-connect-cluster (1)
offset.storage.topic: my-connect-cluster-offsets (2)
config.storage.topic: my-connect-cluster-configs (3)
status.storage.topic: my-connect-cluster-status (4)
key.converter: org.apache.kafka.connect.json.JsonConverter (5)
value.converter: org.apache.kafka.connect.json.JsonConverter (6)
key.converter.schemas.enable: true (7)
value.converter.schemas.enable: true (8)
config.storage.replication.factor: 3 (9)
offset.storage.replication.factor: 3 (10)
status.storage.replication.factor: 3 (11)
# ...
The Kafka Connect cluster ID within Kafka. Must be unique for each Kafka Connect cluster.
Kafka topic that stores connector offsets. Must be unique for each Kafka Connect cluster.
Kafka topic that stores connector and task status configurations. Must be unique for each Kafka Connect cluster.
Kafka topic that stores connector and task status updates. Must be unique for each Kafka Connect cluster.
Converter to transform message keys into JSON format for storage in Kafka.
Converter to transform message values into JSON format for storage in Kafka.
Schema enabled for converting message keys into structured JSON format.
Schema enabled for converting message values into structured JSON format.
Replication factor for the Kafka topic that stores connector offsets.
Replication factor for the Kafka topic that stores connector and task status configurations.
Replication factor for the Kafka topic that stores connector and task status updates.
KafkaConnector
management of connectorsAfter plugins have been added to the container image used for the worker pods in a deployment,
you can use Strimzi’s KafkaConnector
custom resource or the Kafka Connect API to manage connector instances.
You can also create new connector instances using these options.
The KafkaConnector
resource offers a Kubernetes-native approach to management of connectors by the Cluster Operator.
To manage connectors with KafkaConnector
resources, you must specify an annotation in your KafkaConnect
custom resource.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnect
metadata:
name: my-connect-cluster
annotations:
strimzi.io/use-connector-resources: "true"
# ...
Setting use-connector-resources
to true
enables KafkaConnectors to create, delete, and reconfigure connectors.
If use-connector-resources
is enabled in your KafkaConnect
configuration, you must use the KafkaConnector
resource to define and manage connectors.
KafkaConnector
resources are configured to connect to external systems.
They are deployed to the same Kubernetes cluster as the Kafka Connect cluster and Kafka cluster interacting with the external data system.
The configuration specifies how connector instances connect to an external data system, including any authentication. You also need to state what data to watch. For a source connector, you might provide a database name in the configuration. You can also specify where the data should sit in Kafka by specifying a target topic name.
Use tasksMax
to specify the maximum number of tasks.
For example, a source connector with tasksMax: 2
might split the import of source data into two tasks.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnector
metadata:
name: my-source-connector (1)
labels:
strimzi.io/cluster: my-connect-cluster (2)
spec:
class: org.apache.kafka.connect.file.FileStreamSourceConnector (3)
tasksMax: 2 (4)
config: (5)
file: "/opt/kafka/LICENSE" (6)
topic: my-topic (7)
# ...
Name of the KafkaConnector
resource, which is used as the name of the connector. Use any name that is valid for a Kubernetes resource.
Name of the Kafka Connect cluster to create the connector instance in. Connectors must be deployed to the same namespace as the Kafka Connect cluster they link to.
Full name of the connector class. This should be present in the image being used by the Kafka Connect cluster.
Maximum number of Kafka Connect tasks that the connector can create.
Connector configuration as key-value pairs.
Location of the external data file. In this example, we’re configuring the FileStreamSourceConnector
to read from the /opt/kafka/LICENSE
file.
Kafka topic to publish the source data to.
|
Note
|
You can load confidential configuration values for a connector from Kubernetes Secrets or ConfigMaps. |
Use the Kafka Connect REST API as an alternative to using KafkaConnector
resources to manage connectors.
The Kafka Connect REST API is available as a service running on <connect_cluster_name>-connect-api:8083
, where <connect_cluster_name> is the name of your Kafka Connect cluster.
You add the connector configuration as a JSON object.
curl -X POST \
http://my-connect-cluster-connect-api:8083/connectors \
-H 'Content-Type: application/json' \
-d '{ "name": "my-source-connector",
"config":
{
"connector.class":"org.apache.kafka.connect.file.FileStreamSourceConnector",
"file": "/opt/kafka/LICENSE",
"topic":"my-topic",
"tasksMax": "4",
"type": "source"
}
}'
If KafkaConnectors are enabled, manual changes made directly using the Kafka Connect REST API are reverted by the Cluster Operator.
The operations supported by the REST API are described in the Apache Kafka documentation.
|
Note
|
You can expose the Kafka Connect API service outside Kubernetes. You do this by creating a service that uses a connection mechanism that provides the access, such as an ingress or route. Use advisedly as the connection is insecure. |
A Kafka Bridge configuration requires a bootstrap server specification for the Kafka cluster it connects to, as well as any encryption and authentication options required.
Kafka Bridge consumer and producer configuration is standard, as described in the Apache Kafka configuration documentation for consumers and Apache Kafka configuration documentation for producers.
HTTP-related configuration options set the port connection which the server listens on.
The Kafka Bridge supports the use of Cross-Origin Resource Sharing (CORS).
CORS is a HTTP mechanism that allows browser access to selected resources from more than one origin, for example, resources on different domains.
If you choose to use CORS, you can define a list of allowed resource origins and HTTP methods for interaction with the Kafka cluster through the Kafka Bridge.
The lists are defined in the http
specification of the Kafka Bridge configuration.
CORS allows for simple and preflighted requests between origin sources on different domains.
A simple request is a HTTP request that must have an allowed origin defined in its header.
A preflighted request sends an initial OPTIONS HTTP request before the actual request to check that the origin and the method are allowed.
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaBridge
metadata:
name: my-bridge
spec:
# ...
bootstrapServers: my-cluster-kafka:9092
http:
port: 8080
cors:
allowedOrigins: "https://strimzi.io"
allowedMethods: "GET,POST,PUT,DELETE,OPTIONS,PATCH"
consumer:
config:
auto.offset.reset: earliest
producer:
config:
delivery.timeout.ms: 300000
# ...
A secure deployment of Strimzi can encompass:
Encryption for data exchange
Authentication to prove identity
Authorization to allow or decline actions executed by users
Strimzi supports Transport Layer Security (TLS), a protocol for encrypted communication.
Communication is always encrypted for communication between:
Kafka brokers
ZooKeeper nodes
Operators and Kafka brokers
Operators and ZooKeeper nodes
Kafka Exporter
You can also configure TLS between Kafka brokers and clients by applying TLS encryption to the listeners of the Kafka broker. TLS is specified for external clients when configuring an external listener.
Strimzi components and Kafka clients use digital certificates for encryption. The Cluster Operator sets up certificates to enable encryption within the Kafka cluster. You can provide your own server certificates, referred to as Kafka listener certificates, for communication between Kafka clients and Kafka brokers, and inter-cluster communication.
Strimzi uses Secrets to store the certificates and private keys required for TLS in PEM and PKCS #12 format.
A TLS Certificate Authority (CA) issues certificates to authenticate the identity of a component. Strimzi verifies the certificates for the components against the CA certificate.
Strimzi components are verified against the cluster CA Certificate Authority (CA)
Kafka clients are verified against the clients CA Certificate Authority (CA)
Kafka listeners use authentication to ensure a secure client connection to the Kafka cluster.
Supported authentication mechanisms:
Mutual TLS client authentication (on listeners with TLS enabled encryption)
SASL SCRAM-SHA-512
OAuth 2.0 token based authentication
Custom authentication
The User Operator manages user credentials for TLS and SCRAM authentication, but not OAuth 2.0. For example, through the User Operator you can create a user representing a client that requires access to the Kafka cluster, and specify TLS as the authentication type.
Using OAuth 2.0 token-based authentication, application clients can access Kafka brokers without exposing account credentials. An authorization server handles the granting of access and inquiries about access.
Custom authentication allows for any type of kafka-supported authentication. It can provide more flexibility, but also adds complexity.
Kafka clusters use authorization to control the operations that are permitted on Kafka brokers by specific clients or users. If applied to a Kafka cluster, authorization is enabled for all listeners used for client connection.
If a user is added to a list of super users in a Kafka broker configuration, the user is allowed unlimited access to the cluster regardless of any authorization constraints implemented through authorization mechanisms.
Supported authorization mechanisms:
Simple authorization
OAuth 2.0 authorization (if you are using OAuth 2.0 token-based authentication)
Open Policy Agent (OPA) authorization
Custom authorization
Simple authorization uses AclAuthorizer
, the default Kafka authorization plugin.
AclAuthorizer
uses Access Control Lists (ACLs) to define which users have access to which resources.
For custom authorization, you configure your own Authorizer
plugin to enforce ACL rules.
OAuth 2.0 and OPA provide policy-based control from an authorization server. Security policies and permissions used to grant access to resources on Kafka brokers are defined in the authorization server.
URLs are used to connect to the authorization server and verify that an operation requested by a client or user is allowed or denied. Users and clients are matched against the policies created in the authorization server that permit access to perform specific actions on Kafka brokers.
Monitoring data allows you to monitor the performance and health of Strimzi. You can configure your deployment to capture metrics data for analysis and notifications.
Metrics data is useful when investigating issues with connectivity and data delivery. For example, metrics data can identify under-replicated partitions or the rate at which messages are consumed. Alerting rules can provide time-critical notifications on such metrics through a specified communications channel. Monitoring visualizations present real-time metrics data to help determine when and how to update the configuration of your deployment. Example metrics configuration files are provided with Strimzi.
Distributed tracing complements the gathering of metrics data by providing a facility for end-to-end tracking of messages through Strimzi.
Cruise Control provides support for rebalancing of Kafka clusters, based on workload data.
Strimzi can employ the following tools for metrics and monitoring:
Prometheus pulls metrics from Kafka, ZooKeeper and Kafka Connect clusters. The Prometheus Alertmanager plugin handles alerts and routes them to a notification service.
Kafka Exporter adds additional Prometheus metrics
Grafana provides dashboard visualizations of Prometheus metrics
Jaeger provides distributed tracing support to track transactions between applications
Cruise Control balances data across a Kafka cluster
For more information on the metrics and monitoring tools, refer to the supporting documentation:
{JaegerHome}
Prometheus can extract metrics data from Kafka components and the Strimzi Operators.
To use Prometheus to obtain metrics data and provide alerts, Prometheus and the Prometheus Alertmanager plugin must be deployed. Kafka resources must also be deployed or redeployed with metrics configuration to expose the metrics data.
Prometheus scrapes the exposed metrics data for monitoring. Alertmanager issues alerts when conditions indicate potential problems, based on pre-defined alerting rules.
Sample metrics and alerting rules configuration files are provided with Strimzi. The sample alerting mechanism provided with Strimzi is configured to send notifications to a Slack channel.
Grafana uses the metrics data exposed by Prometheus to present dashboard visualizations for monitoring.
A deployment of Grafana is required, with Prometheus added as a data source. Example dashboards, supplied with Strimzi as JSON files, are imported through the Grafana interface to present monitoring data.
Kafka Exporter is an open source project to enhance monitoring of Apache Kafka brokers and clients. Kafka Exporter is deployed with a Kafka cluster to extract additional Prometheus metrics data from Kafka brokers related to offsets, consumer groups, consumer lag, and topics. You can use the Grafana dashboard provided to visualize the data collected by Prometheus from Kafka Exporter.
A sample configuration file, alerting rules and Grafana dashboard for Kafka Exporter are provided with Strimzi.
Within a Kafka deployment, distributed tracing using Jaeger is supported for:
MirrorMaker to trace messages from a source cluster to a target cluster
Kafka Connect to trace messages consumed and produced by Kafka Connect
Kafka Bridge to trace messages consumed and produced by Kafka Bridge, and HTTP requests from client applications
Template configuration properties are set for the Kafka resources, which describe tracing environment variables.
Client applications, such as Kafka producers and consumers, can also be set up so that transactions are monitored. Clients are configured with a tracing profile, and a tracer is initialized for the client application to use.
Cruise Control is an open source project for simplifying the monitoring and balancing of data across a Kafka cluster. Cruise Control is deployed alongside a Kafka cluster to monitor its traffic, propose more balanced partition assignments, and trigger partition reassignments based on those proposals.
Cruise Control collects resource utilization information to model and analyze the workload of the Kafka cluster. Based on optimization goals that have been defined, Cruise Control generates optimization proposals outlining how the cluster can be effectively rebalanced. When an optimization proposal is approved, Cruise Control applies the rebalancing outlined in the proposal.
You can generate optimization proposals in specific modes.
The full
mode rebalances across all brokers and is used by default.
Other modes accommodate changes when scaling a cluster up or down.
Generate an optimization proposal in add-brokers
mode to move data from existing brokers to newly added ones.
Generate an optimization proposal in remove-brokers
mode to move all data off brokers to be removed, before scaling down.
Prometheus can extract Cruise Control metrics data, including data related to optimization proposals and rebalancing operations. A sample configuration file and Grafana dashboard for Cruise Control are provided with Strimzi.


AWS::IVS::Channel
The AWS::IVS::Channel
resource specifies an Amazon IVS channel. A channel stores configuration information
related to your live stream. For more information, see CreateChannel in the
Amazon Interactive Video Service API Reference.
By default, the IVS API CreateChannel endpoint creates a stream key in addition to a channel. The Amazon IVS Channel resource does not create a stream key; to create a stream key, use the StreamKey resource instead.
Syntax
To declare this entity in your AWS CloudFormation template, use the following syntax:
JSON
{ "Type" : "AWS::IVS::Channel", "Properties" : { "Authorized" :
Boolean
, "LatencyMode" :String
, "Name" :String
, "RecordingConfigurationArn" :String
, "Tags" :[ Tag, ... ]
, "Type" :String
} }
YAML
Type: AWS::IVS::Channel Properties: Authorized:
Boolean
LatencyMode:String
Name:String
RecordingConfigurationArn:String
Tags:- Tag
Type:String
Properties
Authorized
-
Whether the channel is authorized.
Default:
false
Required: No
Type: Boolean
Update requires: No interruption
LatencyMode
-
Channel latency mode. Valid values:
-
NORMAL
: Use NORMAL to broadcast and deliver live video up to Full HD. -
LOW
: Use LOW for near real-time interactions with viewers.
Note In the Amazon IVS console,
LOW
andNORMAL
correspond toUltra-low
andStandard
, respectively.Default:
LOW
Required: No
Type: String
Allowed values:
LOW | NORMAL
Update requires: No interruption
-
Name
-
Channel name.
Required: No
Type: String
Minimum:
0
Maximum:
128
Pattern:
^[a-zA-Z0-9-_]*$
Update requires: No interruption
RecordingConfigurationArn
-
The ARN of a RecordingConfiguration resource. An empty string indicates that recording is disabled for the channel. A RecordingConfiguration ARN indicates that recording is enabled using the specified recording configuration. See the RecordingConfiguration resource for more information and an example.
Default: "" (empty string, recording is disabled)
Required: No
Type: String
Minimum:
0
Maximum:
128
Pattern:
^$|^arn:aws:ivs:[a-z0-9-]+:[0-9]+:recording-configuration/[a-zA-Z0-9-]+$
Update requires: No interruption
Tags
-
An array of key-value pairs to apply to this resource.
For more information, see Tag.
Required: No
Type: List of Tag
Update requires: No interruption
Type
-
The channel type, which determines the allowable resolution and bitrate. If you exceed the allowable resolution or bitrate, the stream probably will disconnect immediately. Valid values:
-
STANDARD
: Video is transcoded: multiple qualities are generated from the original input to automatically give viewers the best experience for their devices and network conditions. Transcoding allows higher playback quality across a range of download speeds. Resolution can be up to 1080p and bitrate can be up to 8.5 Mbps. Audio is transcoded only for renditions 360p and below; above that, audio is passed through. -
BASIC
: Video is transmuxed: Amazon IVS delivers the original input to viewers. The viewer’s video-quality choice is limited to the original input. Resolution can be up to 1080p and bitrate can be up to 1.5 Mbps for 480p and up to 3.5 Mbps for resolutions between 480p and 1080p.
Default:
STANDARD
Required: No
Type: String
Allowed values:
ADVANCED_HD | ADVANCED_SD | BASIC | STANDARD
Update requires: No interruption
-
Return values
Ref
When you pass the logical ID of this resource to the intrinsic Ref
function, Ref
returns the channel ARN. For example:
{ "Ref": "myChannel" }
For the Amazon IVS channel myChannel
,
Ref
returns the channel ARN.
For more information about using the Ref
function, see Ref.
Fn::GetAtt
The Fn::GetAtt
intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values.
For more information about using the Fn::GetAtt
intrinsic function, see Fn::GetAtt.
Arn
-
The channel ARN. For example:
arn:aws:ivs:us-west-2:123456789012:channel/abcdABCDefgh
IngestEndpoint
-
Channel ingest endpoint, part of the definition of an ingest server, used when you set up streaming software.
For example:
a1b2c3d4e5f6.global-contribute.live-video.net
PlaybackUrl
-
Channel playback URL. For example:
https://a1b2c3d4e5f6.us-west-2.playback.live-video.net/api/video/v1/us-west-2.123456789012.channel.abcdEFGH.m3u8
Examples
Channel and Stream Key Template Examples
The following examples specify an Amazon IVS channel and stream key.
JSON
{ "AWSTemplateFormatVersion": "2010-09-09", "Resources": { "Channel": { "Type": "AWS::IVS::Channel", "Properties": { "Name": "MyChannel", "Tags": [ { "Key": "MyKey", "Value": "MyValue" } ] } }, "StreamKey": { "Type": "AWS::IVS::StreamKey", "Properties": { "ChannelArn": {"Ref": "Channel"}, "Tags": [ { "Key": "MyKey", "Value": "MyValue" } ] } } }, "Outputs": { "ChannelArn": { "Value": {"Ref": "Channel"} }, "ChannelIngestEndpoint": { "Value": { "Fn::GetAtt": [ "Channel", "IngestEndpoint" ] } }, "ChannelPlaybackUrl": { "Value": { "Fn::GetAtt": [ "Channel", "PlaybackUrl" ] } }, "StreamKeyArn": { "Value": {"Ref": "StreamKey"} } } }
YAML
AWSTemplateFormatVersion: 2010-09-09 Resources: Channel: Type: AWS::IVS::Channel Properties: Name: MyChannel Tags: - Key: MyKey Value: MyValue StreamKey: Type: AWS::IVS::StreamKey Properties: ChannelArn: !Ref Channel Tags: - Key: MyKey Value: MyValue Outputs: ChannelArn: Value: !Ref Channel ChannelIngestEndpoint: Value: !GetAtt Channel.IngestEndpoint ChannelPlaybackUrl: Value: !GetAtt Channel.PlaybackUrl StreamKeyArn: Value: !Ref StreamKey
See also
-
Channel data type
-
CreateChannel API endpoint


CacheAttack
Main
Network
Files
Gender
Youtube
Code
HUB
Welcome to CacheAttack
History :D
A Timing attack on the browser to try to predict websites that have cached.
Tor mode
Blocker
Websites Visted


Web Interface is Disabled
Insecure authentication over HTTP is disabled by default.
This could be overridden via "hetu.queryeditor-ui.allow-insecure-over-http" property of "etc/config.properties" (e.g. hetu.queryeditor-ui.allow-insecure-over-http=true).
Check
openLooKeng Web Interface
for details and user guides.


Submit sealed reply to Subodh Gupta’s plea: HC
Subodh Gupta seeks ₹5 crore for damage due to alleged defamatory posts
by Staff ReporterThe Delhi High Court on Monday allowed Instagram handle — Herdsceneand — to submit, in a sealed cover, its response to artist Subodh Gupta’s plea seeking removal of alleged defamatory content made against him on the photo-sharing app.
Justice Rajiv Sahai Endlaw also allowed the Instagram account handle to file in a sealed cover its ‘vakalatnama’ (document empowering a lawyer to act for and on behalf of his client) to maintain its interim anonymity and furnish a redacted copy of its response to Mr. Gupta.
The anonymous account holder will have to submit its response to Mr. Gupta’s suit within two weeks.
Mr. Gupta, in his suit, has sought ₹5 crore for damage caused to him and his family due to the alleged defamatory posts.
The HC had earlier directed social media giants, including Google, Facebook and Instagram, to remove anonymous posts containing sexual harassment accusations against Mr Gupta that emerged during last year’s #MeToo movement. It has listed the case for further hearing on January 22, next year.


{% extends 'base.html' %} {% block content %}
On this page participants of {{ camp.title }} can communicate about ridesharing to and from the festival.
Create ride| When | Location | Seats | {% for ride in ride_list %} |
|---|---|---|---|
| {{ ride.when|date:"c" }} | {{ ride.location }} | {{ ride.seats }} | Details {% empty %} |
| No rideshares yet! {% endfor %} |


Spine is a lightweight framework for building JavaScript web applications. Spine gives you a MVC structure and then gets out of your way, allowing you to concentrate on the fun stuff, building awesome web applications.
Spine is opinionated and, although at first glance it might look similar to other frameworks like Backbone, it actually has quite a different take on things.
Spine is tiny, the library comes in at around 500 lines of JavaScript, that's about 2K minified & compressed. However, it's not about size, it's how you use it, and Spine certainly packs a punch!
Latest version: 0.0.4 (minified). Or check out the source code.
So, what's so special about Spine, how does it stand out from the crowd?
But don't take my word for it. Take a look at the source of the example applications below, and decide for yourself.
Spine has a growing set of tutorials, from layouts, tabs and sidebars to forms, updating records and validation.
Spine includes some great example applications to show you what's possible with the framework, and give you a practical understanding of the library. Although Spine is cross browser, it's worth noting that some of the examples are for WebKit based browsers only (Chrome/Safari), due to browser specific CSS (i.e. they'll just look really ugly in other browsers).
Todos is a very simple to-do application. Users can CRUD tasks, and mark them as complete. The source is available on GitHub. [Demo]
Spine contacts is a simple address book, allowing users to CRUD contacts. The full source is available on GitHub. WebKit only. [Demo]
Holla is the most advanced example, and can be found on GitHub. Holla is a group chat application, and uses a combination of Rails, Juggernaut and Spine to let users chat in realtime. WebKit only. [Demo]
Using Spine is very straightforward, simply include the library in the page.
<script src="spine.js" type="text/javascript" charset="utf-8"></script>
Spine has no prerequisites, but is optimized to work with jQuery or Zepto. Certain extensions to Spine, such as the Ajax and Local Storage model persistence, require additional files located inside the lib folder.
Spine's class implementation is one of its features that makes it stand out from the crowd. Rather than copying properties to emulate inheritance, as most libraries, Spine uses JavaScript's native prototypal inheritance. This is how inheritance should be done, and means it's dynamic, properties are resolved at runtime.
Classes are created like so:
var Task = Spine.Class.create();
create()
takes optional arguments of instance properties, and class properties.
Spine.Class.create([instanceProperties, classProperties]);
var User = Spine.Class.create({
name: "Carolin"
});
Alternatively you can add instance properties using include()
, and class properties with extend()
.
var User = Spine.Class.create();
User.extend({
find: function(){ /* ... */ }
});
User.include({
name: "Tonje"
});
Since Spine doesn't use constructor functions, due to limitations with prototypal inheritance, classes are instantiated with init()
.
var User = Spine.Class.create({
name: "Tonje"
});
var user = User.init();
assertEqual( user.name, "Tonje" );
user.name = "Trish";
assertEqual( user.name, "Trish" );
Any arguments passed to init()
will be forwarded to init()
, the classes' instantiation callback.
var User = Spine.Class.create({
init: function(name){
this.name = name;
}
});
User.init("Martina");
assertEqual( user.name, "Martina" );
Sub-classes are created the same way base classes are, with create()
.
var Friend = User.create();
All of the subclass's parent properties are inherited.
var friend = Friend.init("Tim");
assertEqual( friend.name, "Tim" );
Because we're using real prototypal inheritance, properties are resolved dynamically at runtime. This means that you can change the properties of a parent class, and all its sub-classes with reflect those changes immediately.
var User = Spine.Class.create();
var Friend = User.create();
User.include({defaultName: "(empty)"});
assertEqual( Friend.init().defaultName, "(empty)" );
Context changes constantly in JavaScript, and it's very important your code is executing in the correct one. The most common cause of this is with event listeners, where callbacks will be invoked in the context of window
or the element, rather than their original context. To resolve this, Spine's classes provides a few helper functions for maintaing context.
You can pass a function to proxy()
to guarantee that it will be invoked in the current context.
var Tasks = Spine.Class.create({
init: function(){
$("#destroy").click(this.proxy(this.destroy));
},
destroy: function(){ /* ... */ }
});
Or pass multiple function names to proxyAll()
in order to re-write them permanently, so they're always called with the classes' local content.
var Tasks = Spine.Class.create({
init: function(){
this.proxyAll("destroy")
$("#destroy").click(this.destroy);
},
destroy: function(){ /* ... */ }
});
Spine.Events
is the module Spine uses for adding event support to classes. To use it, just include/extend a class with the module.
var Tasks = Spine.Class.create();
Tasks.extend(Spine.Events);
Spine.Events
gives you three functions, bind()
, trigger()
, and unbind()
. All three have a very similar API to jQuery's event handling one, if you're familiar with that. bind(name, callback)
takes a event name and callback. trigger(name, [*data])
takes an event name and optional data to be passed to handlers. unbind(name, [callback])
takes a event name and optional callback.
Tasks.bind("create", function(){ /* ... */ });
Tasks.trigger("create", ["some", "data"]);
You can bind to multiple events by separating them with spaces. Callbacks are invoked in the context the event is associated with.
Tasks.bind("create update destroy", function(){ this.trigger("change") });
You can pass optional data arguments to trigger()
that will be passed onto event callbacks. Unlike jQuery, an event object will not be passed to callbacks.
Tasks.bind("create", function(name){
alert(name);
});
Tasks.trigger("create", "Take out the rubbish");
Although you may never use Spine.Events
in your own classes, you will use it with Spine's models and controllers. We're going to cover those next.
Models are the core to Spine, and absolutely critical to your applications. Models are where your application's data is stored, and where any logic associated with that data is kept. Models should be de-coupled from the rest of your application, and completely independent. The data associated with models is stored in memory under Model.records
.
Creating models is slightly different from creating classes, since the create()
function is already reserved by models. Models are created with the setup()
function, passing in the model name and an array of attributes.
var Contact = Spine.Model.setup("Contact", ["first_name", "last_name"]);
Models are Spine classes, so you can treat them as such, extending and including properties.
Contact.include({
fullName: function(){
return(this.first_name + " " + this.last_name);
}
});
Model instances are created with init()
, passing in an optional set of attributes.
var contact = Contact.init({first_name: "Alex", last_name: "MacCaw"});
assertEqual( contact.fullName(), "Alex MacCaw" );
Once an instance is created it can be saved in memory by calling save()
.
var Contact = Spine.Model.setup("Contact", ["first_name", "last_name"]);
var contact = Contact.init({first_name: "Joe"});
contact.save();
When a record is saved, Spine automatically creates an ID if it doesn't already exist.
assertEqual( contact.id, "AD9408B3-1229-4150-A6CC-B507DFDF8E90" );
You can use this ID to retrieve the saved record using find()
.
var identicalContact = Contact.find( contact.id );
assert( contact.eql( identicalContact ) );
If find()
fails to retrieve a record, an exception will be thrown. You can check for the existence of records without fear of an exception by calling exists()
.
assert( Contact.exists( contact.id ) );
Once you've changed any of a record's attributes, you can update it in-memory by re-calling save()
.
var contact = Contact.create({first_name: "Polo"});
contact.save();
contact.first_name = "Marko";
contact.save();
You can also use first()
or last()
on the model to retrieve the first and last records respectively.
var firstContact = Contact.first();
To retrieve every contact, use all()
.
var contacts = Contact.all();
for (var i=0; i < contacts.length; i++)
console.log( contacts[i].first_name );
You can pass a function that'll be iterated over every record using each()
.
Contact.each(function(con){
console.log( con.first_name );
});
Or select a subset of records with select()
.
Contact.select(function(con){
if (con.first_name) return true;
});
Validating models is dirt simple, simply override the validate()
function with your own custom one.
Contact.include({
validate: function(){
if (!this.first_name)
return "First name is required";
}
});
If validate()
returns anything, the validation will fail and an error event will be fired on the model. You can catch this by listening for it on the model, notifying the user.
Contact.bind("error", function(rec, msg){
alert("Contact failed to save - " + msg);
});
In addition, save()
, create()
and updateAttributes()
will all return false if validation fails. For more information about validation, see the form tutorial.
Spine's models include special support for JSON serialization. To serialize a record, call JSON.stringify()
passing the record, or to serialize every record, pass the model.
JSON.stringify(Contact);
JSON.stringify(Contact.first());
Alternatively, you can retrieve an instance's attributes and implement your own serialization by calling attributes()
.
var contact = Contact.init({first_name: "Leo"});
assertEqual( contact.attributes(), {first_name: "Leo"} );
Contact.include({
toXML: function(){
return serializeToXML(this.attributes());
}
});
If you're using an older browser which doesn't have native JSON support (i.e. IE 7), you'll need to include json2.js which adds legacy support.
While storing records in memory is useful for quick retrieval, persisting them in one way or another is often required. Spine includes a number of pre-existing storage modules, such as Ajax and HTML5 Local Storage, which you can use for persistence. Alternatively you can roll your own custom one, take a look at spine.ajax.js
for inspiration.
Spine's persistence is implemented via modules, so for HTML5 Local Storage persistence you'll need to include spine.local.js script in the page and for Ajax persistence you'll need spine.ajax.js.
To persist a model using HTML5 Local Storage, simply extend it with Spine.Model.Local
.
Contact.extend(Spine.Model.Local);
When a record is changed, the Local Storage database will be updated to reflect that. In order to fetch the records from Local Storage in the first place, you need to use fetch()
.
Contact.fetch();
Typically this is called once, when your application is first initialized.
Using Ajax as a persistence mechanism is very similar, extend models with Spine.Model.Ajax
.
Contact.extend(Spine.Model.Ajax);
By convention, this uses a basic pluralization mechanism to generate an endpoint, in this case /contacts
. You can choose a custom URL by setting the url
property on your model, like so:
Contact.extend({
url: "/users"
});
Spine will use this endpoint URL as a basis for all of its Ajax requests. Once a model has been persisted with Ajax, whenever its records are changed, Spine will send an Ajax request notifying the server. Spine encodes all of its request's parameters with JSON, and expects JSON encoded responses. Spine uses REST to determine the method and endpoint of HTTP requests, and will work seamlessly with REST friendly frameworks like Rails.
read → GET /collection
create → POST /collection
update → PUT /collection/id
destroy → DELETE /collection/id
For example, after a record has been created client side Spine will send off an HTTP POST to your server, including a JSON representation of the record. Let's say we created a Contact
with a name of "Lars"
, this is the request that would be send to the server:
POST /contacts HTTP/1.0
Host: localhost:3000
Origin: http://localhost:3000
Content-Length: 59
Content-Type: application/json
{"id":"E537616F-F5C3-4C2B-8537-7661C7AC101E","name":"Lars"}
Likewise destroying a record will trigger a DELETE request to the server, and updating a record will trigger a PUT request. For PUT and DELETE requests, the records ID is referenced inside the URL.
PUT /tasks/E537616F-F5C3-4C2B-8537-7661C7AC101E HTTP/1.0
Host: localhost:3000
Origin: http://localhost:3000
Content-Length: 60
Content-Type: application/json
{"id":"44E1DB33-2455-4728-AEA2-ECBD724B5E7B","name":"Peter"}
As you can see, the record's attributes aren't prefixed by the record's model name. This can cause problems with frameworks like Rails, which expect parameters in a certain format. You can fix this, by setting the ajaxPrefix
option.
Spine.Model.ajaxPrefix = true;
If ajaxPrefix
is true, Spine will send requests like the following, prefixing all the attributes with the model name.
PUT /tasks/E537616F-F5C3-4C2B-8537-7661C7AC101E HTTP/1.0
Host: localhost:3000
Origin: http://localhost:3000
Content-Length: 73
Content-Type: application/json
{"contact": {"id":"44E1DB33-2455-4728-AEA2-ECBD724B5E7B","name":"Peter"}}
It's worth mentioning here one of the major differences between Spine and other similar frameworks. All of Spine's server communication is asynchronous - that is Spine never waits for a response. Requests are sent after the operation has completed successfully client side. In other words, a POST request will be sent to the server after the record has successfully saved client side, and the UI has updated. The server is completely de-coupled from the client, clients don't necessarily need a server in order to function.
This might seem like an odd architectural decision at first, but let me explain. Having a de-coupled server offers some clear advantages. Firstly, clients have a completely non-blocking interface, they're never waiting for a slow server response for further interaction with your application. User's don't have to know, or care, about server requests being fired off in the background - they can continue using the application without any loading spinners.
The second advantage is that a de-coupled server greatly simplifies your code. You don't need to cater for the scenario that the record may be displayed in your interface, but isn't editable until a server response returns. Lastly, if you ever decided to add offline support to your application, having a de-coupled server makes this a doddle.
Obviously there are caveats for those advantages, but I think those are easily addressed. Server-side model validation is a contentious issue, for example - what if that fails? However, this is solved by client-side validation. Validation should fail before a record ever gets sent to the server. If validation does fail server-side, it's an error in your client-side validation logic rather than with user input.
When the server does return an unsuccessful response, an ajaxError event will be fired on the model, including the record, XMLHttpRequest object, Ajax settings and the thrown error.
Contact.bind("ajaxError", function(record, xhr, settings, error){
/* Invalid response... */
});
You've already seen that models have some events associated with them, such as error and ajaxError, but what about callbacks to create/update/destroy operations? Well, conveniently Spine includes those too, allowing you to bind to the following events:
For example, you can bind to a model's create event like so:
Contact.bind("create", function(newRecord){
// New record was created
});
For model level callbacks, any associated record is always passed to the callback. The other option is to bind to the event directly on the record.
var contact = Contact.first();
contact.bind("save", function(){
// Contact was updated
});
The callback's context will be the record that the event listener was placed on. You'll find model events crucial when it comes to binding records to the view, making sure the view is kept in sync with your application's data.
One rather neat addition to Spine's models is dynamic records, which use prototypal inheritance to stay updated. Any calls to find()
, all()
, first()
, last()
etc, and model event callbacks return a clone of the saved record. This means that whenever a record is updated, all of its clones will immediately reflect that update.
Let's give you a code example; we're going to create an asset, and a clone of that asset. You'll notice that when the asset is updated, the clone has also automatically changed.
var asset = Asset.create({name: "whatshisname"});
// Completely new asset instance
var clone = Asset.find(asset.id);
// Change saved asset
asset.updateAttributes({name: "bob"});
// Clone reflects changes
assertEqual(clone.name, "bob");
This means that you never have to bother calling some sort of reload()
functions on instances. You can be sure that all instances are constantly in sync with their saved versions.
Controllers are the last part to the trinity of Spine and are very simple, being more of a set of conventions than actual code. Controllers are the glue inside your application, tying the various components together. Generally, controllers deal with adding and responding to DOM events, rendering templates and keeping views and models in sync.
Controllers, like models, extend Spine.Class
and so inherit all of its properties. This means you can use extend()
and include()
to add properties onto controllers, and can take advantage of all of Spine's context management. To create a controller, inherit a class from Spine.Controller
.
var Tasks = Spine.Controller.create({
init: function(){
// Called on instantiation
}
});
The convention inside Spine is to give the controller a plural camel cased name of the model it is most associated with, in this case Tasks
. Usually, you'll only be adding instance properties onto controllers, so you can just pass them as the first argument to create()
. Instantiating controllers is the same as creating an instance of any other class, by calling init()
.
var tasks = Tasks.init();
Every controller has an element associated with it, which you can access under the instance property el
. You can also set this element manually by passing it through on instantiation.
var tasks = Tasks.init({el: $("#tasks")});
In fact, anything you pass to init()
will be set as properties on the newly created instance. For example, you could pass a record that a controller would be associated with.
var taskItem = TaskItem.init({item: Task.first()});
Inside your controller's init()
function, you'll generally add event listeners to models and views, referencing a function inside the controller.
Spine gives you a shortcut for adding event listeners onto DOM elements, with the events
property.
var Tasks = Spine.Controller.create({
events: {"click .item": "click"},
click: function(e){
// Invoked when .item is clicked
}
});
events
is an object in the following format {"eventType selector", "functionName"}
. All the selectors are scoped to the controller's associated element, el
. If a selector isn't provided, the event will be added directly on el
, otherwise it'll be delegated to any children matching the selector.
Spine will take care of callback context for you, making sure it keeps to the current controller. Callbacks will be passed an event object, and you can access the original element the event was targeted on using event.target
.
var Tasks = Spine.Controller.create({
events: {"click .item": "click"},
click: function(event){
var item = jQuery(event.target);
}
});
Since Spine uses delegation for events, it doesn't matter if the contents of el
change. The appropriate events will still be fired when necessary.
As well as DOM events, Spine.Controller
has been extended with Spine.Events
, meaning that you can bind and trigger custom events.
var ToggleView = Spine.Controller.create({
init: function(){
this.items = this.$(".items");
this.items.click(this.proxy(function(){
this.trigger("toggle");
}));
this.bind("toggle", this.toggle);
},
toggle: function(){ /* ... */ }
});
Spine also has a global object Spine.App
, that you can bind and trigger global events. This is one way that you can get controllers communicating with one other, without resorting to deep-coupling. Spine.App
is aliased inside controllers to this.App
.
var GlobalController = Spine.Controller.create({
init: function(){
this.el.click(this.proxy(this.click));
},
click: function(){
this.App.trigger("globalEvent", this.el);
}
});
When you first instantiate a controller, it's common to set a bunch of instance variables referencing various elements. For example, setting the items
variable on the Tasks
controller:
var Tasks = Spine.Controller.create({
init: function(){
this.items = this.$(".items");
}
});
Since this is such a common scenario, Spine provides a helper, the elements
property. The is in the format of {"selector": "variableName"}
. When the controller is instantiated, Spine will go through elements
, setting the appropriate elements as properties on the instance. Like with events
, all the selectors are scoped by the controller's current element, el
.
var Tasks = Spine.Controller.create({
elements: {".items": "items"},
init: function(){
this.items.each(function(){
// ...
 For example:
var Tasks = Spine.Controller.create({
init: function(){
this.proxyAll("render");
Task.change(this.render);
},
render: function(){ /* ... */ }
});
As you've probably guessed, Spine provides a shortcut for adding proxies too, using the proxied
property. Simply set proxied
to an array of function names, and Spine will make sure those functions are always executed in the controller's context.
var Tasks = Spine.Controller.create({
proxied: ["render"],
init: function(){
Task.change(this.render);
}
});
Spine provides application routing based on the URL's hash fragment, for example the URL http://example.com/#/users
has the hash fragment /users
. Hash fragments can be completely arbitrary and don't trigger page reloads, maintaining page state. Your application can also be indexed by hash fragments using Google's Ajax Crawling specification.
Internally Spine uses the hashchange event to detect changes in the URLs hash. This event has only been developed recently, and only available in newer browsers. To support antiquated browsers, you can use the excellent jQuery hashchange plugin, which emulates the event using iframes and other clever trickery.
So, how to use the API? It's very simple, first you need to include spine.route.js, which contains the module Spine.Route
. Then you can start adding routes inside your controller. Spine.Route
gives you a routes()
function inside controllers, which you can call passing a hash of routes and callbacks.
var App = Spine.Controller.create({
init: function(){
this.routes({
"/users/:id": function(id){
// Activate controller or something
console.log("/users/", id)
},
"/users": function(any){
console.log("/users")
}
});
}
}).init();
Route parameters, are in the form of :name
, and are passed as arguments to the associated callback. You can also use globs to match anything via an asterisk, like so:
App.routes({
"/pages/*glob": function(name){
console.log("/pages/", name);
}
});
Routes are added in reverse order of specificity, so the most specific routes should be added first, and generic 'catch alls' should be added later. It's worth noting, especially if you're putting routes in the init()
function of controllers, that routes shouldn't be added more than once. The examples above are fine, since the App
controller is only ever going to be instantiated a single time.
One alternative is to skip out controllers, and add routes directly using Spine.Route.add()
, passing in either a hash or a single route.
Spine.Route.add(/\/groups(\/)?/, function(){
console.log('groups')
});
Like you can see in the example above, routes can also be raw regexes, giving you full control over matching.
When the page loads initially, even if the URL has a hash fragment, the hashchange
event won't be called. It'll only be called for subsequent changes. This means, after our application has been setup, we need to manually tell Spine that we want to run the routes & check the URL's hash. This can be done by invoking Spine.Route.setup()
.
Spine.Route.setup();
Lastly, Spine gives controllers a navigate()
function, which can be passed a fragment to change the URL's hash. You can also pass navigate()
multiple arguments, which will be joined by a forward slash (/
) to create the fragment.
var Users = Spine.Controller.create({
init: function(){
// Navigate to #/users/:id
this.navigate("/users", this.item.id);
}
});
Users.init({item: User.first()});
Using navigate()
ensures that the URL's fragment is kept in sync with the relevant controllers. By default, calling navigate()
won't trigger any events or route callbacks. If you want to trigger routes, pass a true
boolean as the last argument to navigate()
.
// Trigger routes by passing true
Spine.Route.navigate("/users", true);
Spine also gives you the option of using HTML5's History API, which has the advantage of being able to alter the url without a page refresh or using hash fragments. This means cleaner URLs in a format your users are accustomed to.
To use the History API, instead of hash fragments, pass {history: true}
to setup()
:
Spine.Route.setup({history: true});
HTML5 History support will only be enabled if this option is present, and the API is available. Otherwise, Spine's routing will revert back to using hash fragments.
However, there are some things you need to be aware of when using the History API. Firstly, ever URL you send to navigate()
needs to have a real HTML representation. Although the browser won't request the new URL at that point, it will be requested if the page is subsequently reloaded. In other words you can't make up arbitrary URLs, like you can with hash fragments; every URL passed to the API needs to exist. One way of implementing this is with server side support.
When browsers request a URL (expecting a HTML response) you first make sure on server-side that the endpoint exists and is valid. Then you can just serve up the main application, which will read the URL, invoking the appropriate routes. For example, let's say your user navigates to http://example.com/users/1
. On the server-side, you check that the URL /users/1
is valid, and that the User record with an ID of 1
exists. Then you can go ahead and just serve up the JavaScript application.
The caveat to this approach is that it doesn't give search engine crawlers any real content. If you want your application to be crawl-able, you'll have to detect crawler bot requests, and serve them a 'parallel universe of content'. That is beyond the scope of this documentation though.
We've covered all the main options available in controllers, so let's have a look at some typical use cases.
The render pattern is a really useful way of binding models and views together. When the controller is instantiated, it adds an event listener to the relevant model, invoking a callback when the model is refreshed or changed. The callback will update el
, usually by replacing its contents with a rendered template.
var Contacts = Spine.Controller.create({
init: function(){
Contact.bind("refresh change", this.proxy(this.render));
},
template: function(items){
return($("#contactsTemplate").tmpl(items));
},
render: function(){
this.el.html(this.template(Contact.all()));
}
});
This is a simple but blunt method for data binding, updating every element whenever a single record is changed. This is fine for uncomplicated and small lists, but you may find you need more control over individual elements, such as adding event handlers to items. This is where the element pattern comes in.
The element pattern essentially gives you the same functionality as the render pattern, but a lot more control. It consists of two controllers, one that controls a collection of items, and the other deals with each individual item. Let's dive right into the code to give you a good indication of how it works.
var ContactItem = Spine.Controller.create({
// Delegate the click event to a local handler
events: {
"click": "click"
},
// Ensure functions have the correct context
proxied: ["render", "remove"],
// Bind events to the record
init: function(){
this.item.bind("update", this.render);
this.item.bind("destroy", this.remove);
},
// Render an element
render: function(item){
if (item) this.item = item;
this.el.html(this.template(this.item));
return this;
},
// Use a template, in this case via jQuery.tmpl.js
template: function(items){
return($("#contactsTemplate").tmpl(items));
},
// Called after an element is destroyed
remove: function(){
this.el.remove();
},
// We have fine control over events, and
// easy access to the record too
click: function(){ /* ... */ }
});
var Contacts = Spine.Controller.create({
proxied: ["addAll", "addOne"],
init: function(){
Contact.bind("refresh", this.addAll);
Contact.bind("create", this.addOne);
},
addOne: function(item){
var contact = ContactItem.init({item: item});
this.el.append(contact.render().el);
},
addAll: function(){
Contact.each(this.addOne);
}
});
In the example above, Contacts
has responsibility for adding records when they're initially created, and ContactItem
has responsibility for the record's update and destroy events, re-rendering the record when necessary. Albeit more complicated, this gives us some advantages over the previous render pattern.
For one thing, it's more performant; the list doesn't need to be re-drawn whenever a single element changes. Furthermore, we now have a lot more control over individual items. We can place event handlers, as demonstrated with the click
callback, and manage rendering on an item by item basis.
All resources are located in the lib folder in Spine's repository.
Any questions, just ask on the Google Group.
Found a bug? Open a ticket on GitHub.
Spine has a full Jasmine test suite - you can run it from inside the browser here.
Whoah - your API looks really similar to Backbone. Why should I use this instead?
Well, it's true that Spine was inspired by Backbone, an excellent library, and its controller API is very similar. However, the similarities end there. Internally the library works very differently. For example, Spine has no need for Collections
, which are required for pretty much every model in Backbone. Spine provides a class library, and has very different ideas when it comes to server sync. Lastly, Spine is much simpler and half the size, go and check out the source.
What's so good about prototypal inheritance?
Let me give you a visual example of what I mean. Take a Spine class instance, you can follow its __proto__
property through its parents, all the way up to Object
.
Classical class libraries just copy properties to achieve inheritance, resulting in overhead when your class is first loaded, and doesn't allow for dynamically resolving properties.
Doesn't the fact that servers are de-coupled, and clients never wait for a response, cause issues like conflicts? Nope, certainly not in my experience. Ajax requests to the server are sent serially, requests have to finish before the next one is fired, even if the client UI has already updated. For example, if a contact is created and then immediately destroyed, the create POST will complete before the DELETE request is sent. Certainly, there may be scenarios where you need to disable a UI element while waiting for a server response, but this isn't usually required, and so isn't the default inside Spine.
I still don't get the advantages of server de-coupling. Well, it's all about perceived speed - which makes a huge difference to the user experience. Perceived speed is just as important as actual speed since, at the end of the day, this is what users are going to notice. If we take Holla as an example, when a user sends a new chat message, we could wait until the message takes the roundtrip through the server and clients before appending it to the chat log. However, that would introduce a couple of seconds latency between the time a user submitted a new message, and it appearing in their chat log. The application would seem slow, and it would definitely hurt the user experience.
Instead, why not create the new message locally, thereby immediately adding it to the chat log. From a user's perspective, it seems like the message has been sent instantly. User's won't know (or care), that the message hasn't yet been delivered to other clients in the chat room. They'll just be happy with a fast and snappy user experience.
That's why server de-coupling and perceived speed is important, it gives a much better user experience.
0.0.2: 17/04/2011 - first public release
0.0.3: 25/04/2011 - IE support, tests and fixes
0.0.4: 11/05/2011 - Spine.Manager, Spine.List and Spine.Tabs


Three Short Films Shot on the Huawei Mate 30 Series Coming on November 20th - Gizchina.com
by PierreThree Short Films Shot on the Huawei Mate 30 Series Coming on November 20th
3.1 (62.86%) 7 votes
The Huawei Mate 30 series appears to have become one of the most successful devices launched by the Chinese phone maker in the past years. Or at least we assume so by looking at the amount of effort Huawei is putting into its advertising.
Three Short Films Shot on the Huawei Mate 30 Series Coming on November 20th
The latest promotional stunt is indeed quite ambitious. Huawei is planning to launch at total of three movies shot entirely on the Mate 30 Series. The movies are going to unveiled at the 28th Golden Rooster and Hundred Flowers Film Festival taking place in Xiamen, Fujian.
The three movies are part of the program Huawei calls “Mate 30 Reconstructed Imagination” and will be shown on November 20th. The short movies are “Your Look”, “Badain Jaran” and “Swim to Spring”; they’ll be up on the screens of the No. 7 in Vientiane Studio, Xiamen. If you’re in the area, you can actually watch the movies between 1PM and 3PM.
The most interesting movie of the three is probably “Badain Jaran” as it was supervised by one of the directors at the Golden Bear Award of the Berlin Film Festival. While the movie “Swim to Spring” was directer by the young Ren Yongqiang. The story is about the struggle for a young man to find the way back home; the movie has a duration of 20 minutes.
As we previously mentioned, all the clips in the movies have been shot on a device from the Huawei Mate 30 Series, most likely the Mate 30 Pro. The smartphone equips a primary 40MP ( Cine Camera, f/1.8 aperture) plus a 40 MP (SuperSensing Camera, f/1.6 aperture, OIS). We then have 8 MP (Telephoto Camera,OIS, 3x Optical Zoom) and 3D Depth Sensing Camera supports Autofocus. The handset supports AIS (HUAWEI AI Image Stabilization) and can shoot 4k HDR+ and 7680fps ultra slow videos.


An immutable Scala class to represent units of information (file size, disk space, memory). View the test coverage report and Scala API docs here.


CodonPDX is a senior capstone project of the computer science department at Portland State University
Done at the direction of Dr. Ken Steman


jQuery Run App
Home
Add a New Run
Welcome to the app
You can use this app to track your running milage
Your Latest Runs
Clear Run Milage
jQuery Run App
Home
Add a New Run
Add a Run
Enter Miles:
Enter Date:
Submit
jQuery Run App
Home
Add a New Run
Edit a Run
Enter Miles:
Enter Date:
Update


The Foreign Relations window shows the foreign relations status between all the nations in the game.
In this example, we have two other players: Rommel and Patrishimoto. On the left, below Rommel's name
is the description "(98) Good Friends". This is Rommel's attitude towards you. The numerical value ("98")
ranges from 0 (hated) to 100 (friends). Patrishimoto, on the other hand, dislikes you.
The "Your Relations" column shows your official relations with the player. You are at peace with Rommel, and at war with Patrishimoto.
The "Foreign Relations" column shows each players relations to other players. Rommel is not a part of any team, is at war with Patrishimoto,
and has no alliances. Patrishimoto is at war with you ("Sherman") and Rommel.
The "Actions" column lets players interact with that player - declaring war on them, sending
a message, or trading with them (available through the "Send Message" interface).
Foreign Relations can be changed by these actions:
Forming Alliances (through an Agreement; requires permission from the other player),
Peace Agreements (through an Agreement; requires permission from the other player),
Making a Declaration of War (done unilaterally, this switches your nation's policy towards another nation to 'war', the other nation can choose whether or not to change their official status towards you),
Giving your units orders to attack another player (both sides switch to war status when the attack occurs),
Trading with a Player (improves the other players attitude towards you)
Related Topics:
(None)