Meine

Gedanken.

nginx-Konfiguration für JTL Shop 3

Teil eines kürzlich abgeschlossenen Projektes war es, einen recht großen JTL Shop von einem Managed Server mit Apache 2.x auf einen neuen, selbstadministrierten Root-Server mit nginx umzuziehen.

Da JTL momentan ein angenehmes Wachstum verzeichnet und die Shops größer und anspruchsvoller werden, hier eine Beispielkonfiguration (SSL-only):

server {
	listen *:80;
	server_name example.com www.example.com;
	return 301 https://www.example.com$request_uri;
}

server {
	listen *:443 ssl spdy;
	#poodle
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	#avoid BEAST attacks
	ssl_ciphers RC4:HIGH:!aNULL:!MD5;
	ssl_prefer_server_ciphers on;
	ssl_certificate /var/www/host/ssl/cert.crt;
	ssl_certificate_key /var/www/host/ssl/cert.key;
	server_name example.com www.example.com;
	client_max_body_size 64M;
	root /var/www/host/web;
	index index.php index.html index.htm;

	error_page 401 /error/401.html;
	error_page 403 /error/403.html;
	error_page 405 /error/405.html;
	error_page 500 /error/500.html;
	error_page 502 /error/502.html;
	error_page 503 /error/503.html;
	recursive_error_pages on;

	location = /error/401.html {
		internal;
	}
	location = /error/403.html {
		internal;
	}
	location = /error/405.html {
		internal;
	}
	location = /error/500.html {
		internal;
	}
	location = /error/502.html {
		internal;
	}
	location = /error/503.html {
		internal;
	}

	error_log /var/log/nginx/example.com/error.log;
	access_log /var/log/nginx/example.com/access.log combined;

	#Disable .htaccess and other hidden files
	location ~ /\. {
		deny all;
		access_log off;
		log_not_found off;
	}

	location = /favicon.ico {
		log_not_found off;
		access_log off;
	}

	location = /robots.txt {
		allow all;
		log_not_found off;
		access_log off;
	}

	location ~ \.php$ {
		try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
	}

	location @php {
		try_files $uri =404;
		include /etc/nginx/fastcgi_params;
		fastcgi_pass unix:/var/lib/php5-fpm/example.sock;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_intercept_errors on;
	}

	#minify rules
	rewrite ^/minify/([a-z]=.*) /includes/libs/minify/index.php?$1 last;
	
	location / {
		try_files $uri $uri/ /index.php?q=$uri&$args;
	}

	#expires for static files
	location ~* \.(eot|ttf|woff|css|less)$ {
		expires max;
		add_header Access-Control-Allow-Origin *;
		add_header Pragma public;
		add_header Cache-Control "public, must-revalidate, proxy-revalidate";
		access_log off;
		log_not_found off;
	}
	location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
		expires max;
		add_header Pragma public;
		add_header Cache-Control "public, must-revalidate, proxy-revalidate";
		access_log off;
		log_not_found off;
	}

	#allow access to specific in includes
	location /includes {
		location ~cron_inc.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~sitemap.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~preisverlaufgraph.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~preisverlaufgraph_ofc.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~suggest.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~newslettertracker.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~suggestforward.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~libs/minify/index.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~plugins/jtl_amazon/version/(.*)/frontend/ipn.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~plugins/jtl_amazon/version/(.*)/frontend/feed.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~modules/notify.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~modules/paypal/PayPal.class.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}		
		location ~modules/safetypay{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~modules/clickpay/GetOrderInfo.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~modules/clickpay/NotifyShop.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~captcha/captcha.php{
			try_files /c7559434f183900b1b82185a5d459d6c.htm @php;
		}
		location ~libs/kcfinder/browse.php{
			try_files /89c20a338bb23891f9c7f9fb1f4d7a0b.htm @php;
		}
		location ~libs/kcfinder/upload.php{
			try_files /89c20a338bb23891f9c7f9fb1f4d7a0b.htm @php;
		}
		location ~libs/kcfinder/css.php{
			try_files /89c20a338bb23891f9c7f9fb1f4d7a0b.htm @php;
		}
		location ~libs/kcfinder/js_localize.php{
			try_files /89c20a338bb23891f9c7f9fb1f4d7a0b.htm @php;
		}
		location ~ext/uploads_cb.php{
			try_files /89c20a338bb23891f9c7f9fb1f4d7a0b.htm @php;
		}		
		
		#deny access to all other php files
		location ~ \.php$ {
			deny all;
		}
	}

	location /classes {
		location ~ \.php$ {
			deny all;
		}
	}
	location /templates {
		location ~ \.php$ {
			deny all;
		}
		location ~ \.tpl$ {
			deny all;
		}
	}
	location /jtllogs {
		deny all;
	}
	location /update {
		deny all;
	}
	location /uploads {
		deny all;
	}
}

Proxmox-Cluster auf Hetzner-Servern mit zusätzlichen IPs installiern

Da ich es ansonsten eh in 2 Wochen wieder vergessen habe, hier eine kurze Anleitung, um einen Proxmox-Cluster mit 2 Nodes bei Hetzner (hier EX4/EX4S) zu installieren.

Hinweis:

Zum Entstehungszeitpunkt dieses Artikels war Debian Stable noch Squeeze und Proxmox in Version 2 aktuell. Inzwischen ist das natürlich Wheezy bzw. Proxmox 3.

Die grundlegenden Schritte haben sich allerdings nicht geändert und sind prinzipiell übertragbar. Der Kernel ist inzwischen nur pve-kernel-2.6.32-22-pve, die Adresse des Repos lautet

deb http://download.proxmox.com/debian wheezy pve
und die Apache-Konfiguration kann entfallen, da Proxmox seinen eigenen Webserver mitbringt.

Meine erste Neuinstallation eines Proxmox-Clusters auf EX40-Systemen von Hetzner lief allerdings nur mit dem Kernel aus den Proxmox-Testing-Repositories, (dort gibt’s den -23-pve Kernel, der mit der Haswell-CPU der neuen Hetzner-Server zurecht kommt).

Grundinstallation

  1. Aus dem Rescue-System per
    # installimage
    das Debian64-squeeze-minimal-Image installieren (Proxmox-Image funktioniert momentan nicht, da durch IPv6 scheinbar der Hostname nicht aufgelöst werden kann).
  2. Als Hostname einen FQDN festlegen (host1.example.com)
  3. Ich habe mich für LVM entschieden, also im Partitionsteil sowas wie
    PART /boot ext3 256M
    PART lvm vg0 all
    LV vg0	root	/	ext4		50G
    LV vg0	swap	swap	swap		16G
    LV vg0	tmp		/tmp	reiserfs	10G
    LV vg0	home	/home	ext4		50G
    
    angeben, speichern, beenden, warten, neubooten, ins Produktivsystem einloggen.
  4. # passwd
    und Passwort in ein ordentliches ändern.
  5. optional:
    # nano /etc/ssh/sshd_config
    und SSH-Port ändern
    # nano /etc/ssh/ssh_config
    und auch hier SSH-Port auf denselben ändern (ggf. in einem eigenen Host-Abschnitt, anschließend
    # /etc/init.d/ssh restart
  6. # echo "deb deb http://download.proxmox.com/debian squeez pve" >> /etc/apt/sources.list
  7. # wget -O- "http://download.proxmox.com/debian/key.asc" | apt-key add -
    aptitude update && aptitude full-upgrade
  8. # aptitude install pve-firmware
    # aptitude install pve-kernel-2.6.32-19-pve
    – dabei Vorschlag zur Konfliktlösung akzeptieren.
  9. # reboot
  10. mit
    # uname -a
    prüfen, ob richtiger Kernel gebootet wurde (sollte hier 2.6.32-19-pve ergeben)
  11. optional:
    # aptitude install pve-headers-2.6.32-19-pve
  12. # aptitude install ntp ssh lvm2 postfix ksm-control-daemon vzprocps open-iscsi
  13. # aptitude install proxmox-ve-2.6.32
    # a2ensite pve-redirect.conf
    # /etc/init.d/apache2 restart
  14. https://host1.exmple.com:8006 im Browser aufrufen - es sollte das Webinterface erscheinen
  15. Anschließend diese Schritte für Host2 wiederholen
  16. Cluster

    1. Host1: Public Key aus /root/.ssh/id_rsa.pub kopieren und auf Host2 in /root/.ssh/authorized_keys einfügen
    2. Host2: Public Key aus /root/.ssh/id_rsa.pub kopieren und auf Host1 in /root/.ssh/authorized_keys einfügen
    3. Host1:
      # nano /etc/hosts
      <ip-von-host2> host2 host2.exmple.com
    4. Host2:
      # nano /etc/hosts
      <ip-von-host1> host1 host1.exmple.com
    5. In meinem Fall musste ich jeweils auch die IPv6-Adresse des Hosts in der letzten Zeile auskommentieren, da es sonst bei der Proxmox-Installation Probleme gab.
    6. Host1:
      # ssh host2
      , "yes", "exit".
    7. Host2:
      # ssh host1
      , "yes", "exit".
    8. optional (nur Linux/OSX): den Public Key vom eigenen Rechner aus ~/.ssh/id_rsa.pub in /root/.ssh/authorized_keys auf jedem Node einfügen, in einem neuen Terminal
      # ssh root@<server-ip>:<ssh-port> 
      und prüfen, ob Login ohne Passwort möglich, dann "PasswordAuthentication no" in /etc/ssh/sshd_config auf host1 und host2, wenn der Test und die beiden vorigen Schritte erfolgreich waren und
      # /etc/init.d/ssh restart
    9. Auf beiden Hosts:
      echo 1 > /proc/sys/net/ipv4/ip_forward
      echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
      echo 1 > /proc/sys/net/ipv6/conf/all/proxy_ndp
      echo 'net.ipv6.conf.all.proxy_ndp=1' >> /etc/sysctl.conf
    10. Netzwerkkonfiguration: Die /etc/network/interfaces anpassen:
      # cp /etc/network/interfaces /etc/network/interfaces.backup
      # nano /etc/network/interfaces
      auto lo
      iface lo inet loopback
      
      auto  eth0
      iface eth0 inet manual
      
      #bridge for public ips
      auto vmbr0
      iface vmbr0 inet static
              address <host-ip>
              network <host-ip-hinten-0>
              netmask <host-netmask>
              broadcast <host-broadcast>
              gateway <host-gateway>
              bridge_ports eth0
              bridge_fd 9
              bridge_hello 2
              bridge_maxage 12
              bridge_stp off
      
      # default route to access subnet
      up route add -net <ip-wie-vorher> netmask <netmask-wie-vorher> gw <andere-ip-wie-vorher> eth0
      
      #bridge for private ips
      auto vmbr1
      iface vmbr1 inet static
      	address  192.168.0.1
      	netmask  255.255.255.0
      	bridge_ports none
      	bridge_stp off
      	bridge_fd 0
      	post-up iptables -t nat -A POSTROUTING -s '192.168.0.0/24' -o vmbr0 -j MASQUERADE
      	post-down iptables -t nat -D POSTROUTING -s '192.168.0.0/24' -o vmbr0 -j MASQUERADE
      
    11. Beide Hosts:
      # pvecm create <NodeName>
      Laut Anleitung sollte dies beim zweiten Node eigentlich nicht gemacht werden, sondern ein add reichen; dies hat bei mir allerdings nicht ganz geklappt.
      # reboot
    12. Da bei Hetzner üblicherweise keine Multicast möglich ist, hier ein Tipp zu Verwendung von Unicast:
      • Node1:
        # nano /etc/pve/cluster.conf
        , "config_version" um 1 erhöhen, <cman keyfile="/var/lib/pve-cluster/corosync.authkey" > ändern in <cman keyfile="/var/lib/pve-cluster/corosync.authkey" transport="udpu">
        # reboot
      • Node2: nach Reboot von node1:
        # pvecm add <node1> -f
        Das -f ist nur nötig, falls zuvor ein Cluster per create erstellt wurde.
      • Ggf. beide Hosts rebooten

    Anschließend sollten im Webinterface beide Nodes auftauchen.

    Nun geht’s an die Einrichtung der VMs:

    1. Unter debian.org den Link für ein Installimage raussuchen und kopieren (oder natürlich Link für iso jeder beliebieger Distribution bzw. Windows-Installation).
    2. # wget -P /var/lib/vz/template/iso/ <image-url>
    3. Im Robot MAC-Adressen für die zusätzlichen IPs beantragen, kopieren.
    4. Sobald das Image heruntergeladen ist, im Webinterface auf Create VM klicken, den zuständige Node auswählen, Name eingeben, als OS Linux 3.X,unter CD/DVD das gerade heruntergeladene Image auswählen, Harddisk virtio und Storage vg0, CPU und Speicher nach Belieben, und im Network-Tab ebenfalls virtio einstellen, die eben erstellte MAC-Adresse eintragen und vmbr0 auswählen.
    5. Falls vg0 im Storage nicht gefunden wird, muss es im Datacenter unter „Storage“ –> Add–> LVM evtl. erst noch hinzugefügt werden
    6. Anschließend die VM starten und über die Console die Installation durchführen
    7. Falls man hier so etwas wie

      no connection : Connection timed out
      TASK ERROR: command '/bin/nc -l -p 5900 -w 10 -c '/usr/bin/ssh -T -o BatchMode=yes 
      1.2.3.4 /usr/sbin/qm vncproxy 101 2>/dev/null'' failed: exit code 1
      erhält, bietet sich folgendes an:

      Im entsprechenden Host in der Datei /etc/pve/nodes//qemu-server/<vm-id>.conf die Zeile

      args: -vnc 0.0.0.0:100,meinpasswort
      hinzufügen

      Anschließend VM neustarten und per externem VNC-Viewer über <host-ip>:6000 mit gewähltem Passwort die Installation durchführen (die 100 im Argument bedeutet „Port 5900 + 100“, falls also mehrere VMs per VNC erreichbar sein sollen, muss dieser Wert entsprechend unterschiedlich ausfallen).

      Der Fehler scheint durch das Java-Applet zu kommen. Bei meinen Tests hat die Console im Webinterface nur im InternetExplorer unter Windows funktioniert.

      Falls man in der VM Windows installieren möchte, vorher noch unter http://alt.fedoraproject.org/pub/alt/virtio-win/latest/images/bin/ die aktuellen VirtIO-Treiber herunterladen und im gleichen Verzeichnis wie die Installations-Images speichern, anschließend unter Hardware-Tab der VM ein weiteres CD/DVD-Laufwerk mit diesem ISO hinzufügen und bei der Windows-Installation als Quelle für zusätzliche Treiber angeben.

    Bei VMs mit privater IP erfolgt die Installation analog, nur dass keine MAC angegeben wird und vmbr1 ausgewählt werden muss.

    # The loopback network interface
    auto lo
    iface lo inet loopback
    
    # The primary network interface
    allow-hotplug eth0
    iface eth0 inet static
    	address 192.168.0.33
    	netmask 255.255.255.0
    	gateway 192.168.0.1
    	pointopoint 192.168.0.1
    

    Anschließend noch

    nameserver 213.133.100.100
    nameserver 213.133.98.98
    nameserver 213.133.99.99
    
    in die /etc/resolv.conf eintragen.

    Nach einem Neustart sollte der Zugriff aufs Internet dann auch von dort aus funktionieren.

    Falls was schief geht

    Sollte der Server nach einem der Schritte nicht mehr erreichbar sein, im Robot das Rescue-System aktivieren (und im Falle von Linux/OSX unter ~/.ssh/know_hosts den entsprechenden Eintrag des Servers löschen), Reset-Auftrag versenden, einloggen und per

    # mkdir /mnt/rescue && mount /dev/vg0/root /mnt/rescue
    die Root-Partition mounten und die letzte Änderung rückgängig machen.

    Sollten sich die Proxmox-Nodes gegenseitig nicht erreichen können, wird das Verzeichnis /etc/pve read-only gemountet. Schlägt also der Befehl touch /etc/pve/testfile oder /etc/init.d/apache2 restart fehl, stimmt hier etwas nicht.

    Dann am besten testen, ob man per SSH wirklich von jedem Node auf den anderen kommt. Meisten lässt sich das Problem mit einem Neustart beheben.

    Goodies

    • Zur Absicherung auf Node2 in der /etc/apache2/sites-available/pve.conf im Abschnitt <Location /index.pl> folgende Zeilen einfügen:
      order deny,allow
      deny from all
      allow from <ip-node1>
      

      Auf Node1 im gleichen Abschnitt dann am besten eine normale .htpasswd-Authentifizierung vorschalten.

    • Auch auf einer VM die nur eine private IP hat, lässt sich ein von außen zugängilicher Webserver betreiben.
      Dazu im Falle von Squeeze auf dem Proxmox-Host folgende Zeilen in die /etc/apt/sources.list eintragen:
      deb http://packages.dotdeb.org squeeze all
      deb-src http://packages.dotdeb.org squeeze all

      Anschließend GPG-Key importieren:

      # wget http://www.dotdeb.org/dotdeb.gpg
      # cat dotdeb.gpg | apt-key add -

      Den proxmox-redirect deaktivieren:

      # a2dissite pve-redirect.conf

      Dann nginx installieren:

      # aptitude update && aptitude install nginx-light

      In der /etc/nginx/sites-available/default vor der server{}-Anweisung:

      upstream myprivateapache  {
            server 192.168.0.3:80; #private IP der VM und Port des Webservers
      }
      

      Und innerhalb der server{}-Anweisung:

      location / {
           proxy_pass  http://myprivateapache;
           proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
           proxy_redirect off;
           proxy_buffering off;
           proxy_set_header        Host            $host;
           proxy_set_header        X-Real-IP       $remote_addr;
           proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      }
      

      Anschließend werden alle Anfragen auf Port 80 der Host-IP an die VM durchgereicht. Dies lässt sich dann natürlich noch für einzelne Domains etwas feiner einstellen, ist aber schonmal ein Anfang.

      Alternativ geht das sicherlich auch mit dem bereits installierten Apache und mod_proxy.

    • Backups mit vzdump einrichten:

      Im Datacenter unter „Storage“ ein neues Storage für Backups einrichten, anschließend

      # crontab -e
      0 5 * * 0 /usr/bin/vzdump 100 --maxfiles 10 --remove 1 --mode snapshot --compress gzip 
      --storage <storage-name> --node <node-name> --mailto me@example.com > /dev/null
      

      100 entspricht dabei der ID des zu sichernden Nodes. Dieser Cronjob erstellt dann jeden Sonntag um 5 einen gzip-komprimierten Snapshot der VM und schickt einen feinen Bericht an die angegebene Mail-Adresse.

      Praktischer Weise tauchten diese Jobs auch im Task-Log des Proxmox-Webinterfaces auf.

Tausche CPU gegen Speicher: KSM im Einsatz

Da mir auf einem meiner KVM-Hosts der Speicher von 8GB langsam knapp wurde, ich diesen aber ungern aufrüsten wollte, musste eine Software-Lösung her: KSM (Kernel Samepage Merging), das versucht, gleiche Memory Pages unterschiedlicher virtueller Maschinen zusammenzufassen.

Da auf dem (Debian Squeeze) Host 4 gleiche VMs (auch jeweils Squeeze) laufen, sollte das ganze also funktionieren. Das Aktivieren ist denkbar einfach:

Per

# grep KSM /boot/config-`uname -r`

prüfen, ob der Kernel KSM unterstützt (sollte ab Kernel 2.6.32 die Ausgabe „CONFIG_KSM=y“ erzeugen),

# echo 1 > /sys/kernel/mm/ksm/run

zum Aktivieren, anschließend warten und mit z.B.

# for ii in /sys/kernel/mm/ksm/* ; do echo -n "$ii: " ; cat $ii ; done
überprüfen, ob alles läuft. Bei mir sieht es ungefähr so aus:

/sys/kernel/mm/ksm/full_scans: 2351
/sys/kernel/mm/ksm/max_kernel_pages: 511889
/sys/kernel/mm/ksm/pages_shared: 102204
/sys/kernel/mm/ksm/pages_sharing: 259801
/sys/kernel/mm/ksm/pages_to_scan: 100
/sys/kernel/mm/ksm/pages_unshared: 714673
/sys/kernel/mm/ksm/pages_volatile: 29091
/sys/kernel/mm/ksm/run: 1
/sys/kernel/mm/ksm/sleep_millisecs: 20

Recht gut sehen kann man die Auswirkungen anschließend mit Munin (hier ab Mitte von Woche 30):

Immerhin ca. 1,5GB mehr verfügbarer Speicher – bei allerdings deutlich höherer CPU-Last, was in diesem Fall aber verschmerzbar ist, da sich die Prozessoren eh größtenteils langweilen.

Magento: CMS-Seite mit zufälligen Produkten und Layered Navigation

Mit der Standard-Random.php von Magento lässt sich leider die Layered Navigation (Filternavigation) nicht korrekt nutzen. Sie scheint zwar, falls man sie im CMS unter Layoutänderungen z.B. folgendermaßen einstellt,

<reference name="content">
<block type="catalog/layer_view" name="catalog.leftnav" before="-" template="catalog/layer/view.phtml"/>
</reference>

filtert anschließend jedoch leider nicht.

Abhilfe schafft eine Anpassung, deren Inspiration ich aus der normalen app/code/Mage/Catalog/Block/Product/List.php habe.

Dazu wird unter app/code/local/Mage/Catalog/Block/Product/List die Datei Random.php erstellt, um die magentoeigene zu überschreiben.

<?php
/**
 * Catalog product random items block
 *
 * @category   Mage
 * @package    Mage_Catalog
 */
class Mage_Catalog_Block_Product_List_Random extends Mage_Catalog_Block_Product_List
{
	protected function _getProductCollection()
	{
		if (is_null($this--->_productCollection)) {
			$layer = $this->getLayer();
			if ($this->getShowRootCategory()) {
				$this->setCategoryId(Mage::app()->getStore()->getRootCategoryId());
			}
			$origCategory = null;
			if ($this->getCategoryId()) {
				$category = Mage::getModel('catalog/category')->load($this->getCategoryId());
				if ($category->getId()) {
					$origCategory = $layer->getCurrentCategory();
					$layer->setCurrentCategory($category);
				}
			}
			$this->_productCollection = $layer->getProductCollection();
			$this->_productCollection->getSelect()->order(new Zend_Db_Expr('RAND()'));
			$this->prepareSortableFieldsByCategory($layer->getCurrentCategory());
			if ($origCategory) {
				$layer->setCurrentCategory($origCategory);
			}
		}
		return $this->_productCollection;
	}    
}


wobei Zeile 26 der entscheidende Unterschied zur normalen Liste ist.

Nun kann man eine CMS-Seite erstellen, beispielsweise „Zufällige Produkte“, schreibt ein bisschen Inhalt und gibt unter Gestaltung -> XML für Layoutänderungen folgendes ein:

<reference name="content">
    <block type="catalog/product_list_random" name="my_list" template="catalog/product/list.phtml">
    </block>
<block type="catalog/layer_view" name="catalog.leftnav" before="-" template="catalog/layer/view.phtml"/>
</reference>

Ggf. muss der Layer_view-Block natürlich in eine andere Referenz, aber das hängt vom verwendeten Theme ab.

Magento-Schnipsel: Vorschaubilder in Vergleichsbox

<ol id="compare-items">
	<?php foreach($this->helper('catalog/product_compare')->getItemCollection() as $_index=>$_item): ?>
		<li>
			<input type="hidden" value="<?php echo $_item->getId() ?>" />
			<a href="<?php echo $_item->getProductUrl() ?>">
				<img src="<?php echo $this->helper('catalog/image')->init(Mage::getModel('catalog/product')->load($_item->getId()), 'small_image')->resize(50, 50) ?>" title="<?php echo $this->htmlEscape($_item->getName()) ?>" alt="<?php echo $this->htmlEscape($_item->getName()) ?>" height="50" width="50" />
			</a>
		</li>
	<?php endforeach; ?>
</ol>